mirror of
https://github.com/LemmyNet/lemmy.git
synced 2024-06-01 19:31:48 +00:00
Merge branch 'main' into cache-local-site-read
This commit is contained in:
commit
b6c72fcc27
|
@ -5,7 +5,8 @@ variables:
|
|||
- &rust_image "rust:1.76"
|
||||
- &install_pnpm "corepack enable pnpm"
|
||||
- &slow_check_paths
|
||||
- path:
|
||||
- event: pull_request
|
||||
path:
|
||||
include: [
|
||||
# rust source code
|
||||
"crates/**",
|
||||
|
@ -40,21 +41,29 @@ steps:
|
|||
- apk add git
|
||||
- git submodule init
|
||||
- git submodule update
|
||||
when:
|
||||
- event: pull_request
|
||||
|
||||
prettier_check:
|
||||
image: tmknom/prettier:3.0.0
|
||||
commands:
|
||||
- prettier -c . '!**/volumes' '!**/dist' '!target' '!**/translations' '!api_tests/pnpm-lock.yaml'
|
||||
when:
|
||||
- event: pull_request
|
||||
|
||||
toml_fmt:
|
||||
image: tamasfe/taplo:0.8.1
|
||||
commands:
|
||||
- taplo format --check
|
||||
when:
|
||||
- event: pull_request
|
||||
|
||||
sql_fmt:
|
||||
image: backplane/pgformatter:latest
|
||||
commands:
|
||||
- ./scripts/sql_format_check.sh
|
||||
when:
|
||||
- event: pull_request
|
||||
|
||||
cargo_fmt:
|
||||
image: rustlang/rust:nightly
|
||||
|
@ -64,6 +73,8 @@ steps:
|
|||
commands:
|
||||
- rustup component add rustfmt
|
||||
- cargo +nightly fmt -- --check
|
||||
when:
|
||||
- event: pull_request
|
||||
|
||||
cargo_machete:
|
||||
image: rustlang/rust:nightly
|
||||
|
@ -73,6 +84,8 @@ steps:
|
|||
- cp cargo-binstall /usr/local/cargo/bin
|
||||
- cargo binstall -y cargo-machete
|
||||
- cargo machete
|
||||
when:
|
||||
- event: pull_request
|
||||
|
||||
ignored_files:
|
||||
image: alpine:3
|
||||
|
@ -80,6 +93,8 @@ steps:
|
|||
- apk add git
|
||||
- IGNORED=$(git ls-files --cached -i --exclude-standard)
|
||||
- if [[ "$IGNORED" ]]; then echo "Ignored files present:\n$IGNORED\n"; exit 1; fi
|
||||
when:
|
||||
- event: pull_request
|
||||
|
||||
# make sure api builds with default features (used by other crates relying on lemmy api)
|
||||
check_api_common_default_features:
|
||||
|
@ -200,7 +215,8 @@ steps:
|
|||
- cat target/log/lemmy_*.out || true
|
||||
- "# If you can't see all output, then use the download button"
|
||||
when:
|
||||
- status: [failure]
|
||||
- event: pull_request
|
||||
status: failure
|
||||
|
||||
publish_release_docker:
|
||||
image: woodpeckerci/plugin-docker-buildx
|
||||
|
@ -247,7 +263,8 @@ steps:
|
|||
- apk add curl
|
||||
- "curl -d'Lemmy CI build failed: ${CI_PIPELINE_URL}' ntfy.sh/lemmy_drone_ci"
|
||||
when:
|
||||
- status: [failure]
|
||||
- event: pull_request
|
||||
status: failure
|
||||
|
||||
notify_on_tag_deploy:
|
||||
image: alpine:3
|
||||
|
|
|
@ -17,13 +17,16 @@ import {
|
|||
deleteAllImages,
|
||||
delta,
|
||||
epsilon,
|
||||
followCommunity,
|
||||
gamma,
|
||||
getSite,
|
||||
imageFetchLimit,
|
||||
registerUser,
|
||||
resolveBetaCommunity,
|
||||
resolveCommunity,
|
||||
resolvePost,
|
||||
setupLogins,
|
||||
waitForPost,
|
||||
unfollows,
|
||||
} from "./shared";
|
||||
const downloadFileSync = require("download-file-sync");
|
||||
|
@ -209,6 +212,11 @@ test("Images in remote post are proxied if setting enabled", async () => {
|
|||
test("No image proxying if setting is disabled", async () => {
|
||||
let user = await registerUser(beta, betaUrl);
|
||||
let community = await createCommunity(alpha);
|
||||
let betaCommunity = await resolveCommunity(
|
||||
beta,
|
||||
community.community_view.community.actor_id,
|
||||
);
|
||||
await followCommunity(beta, true, betaCommunity.community!.community.id);
|
||||
|
||||
const upload_form: UploadImage = {
|
||||
image: Buffer.from("test"),
|
||||
|
@ -228,15 +236,19 @@ test("No image proxying if setting is disabled", async () => {
|
|||
).toBeTruthy();
|
||||
expect(post.post_view.post.body).toBe("![](http://example.com/image2.png)");
|
||||
|
||||
let gammaPost = await resolvePost(delta, post.post_view.post);
|
||||
expect(gammaPost.post).toBeDefined();
|
||||
let betaPost = await waitForPost(
|
||||
beta,
|
||||
post.post_view.post,
|
||||
res => res?.post.alt_text != null,
|
||||
);
|
||||
expect(betaPost.post).toBeDefined();
|
||||
|
||||
// remote image doesnt get proxied after federation
|
||||
expect(
|
||||
gammaPost.post!.post.url?.startsWith("http://127.0.0.1:8551/pictrs/image/"),
|
||||
betaPost.post.url?.startsWith("http://127.0.0.1:8551/pictrs/image/"),
|
||||
).toBeTruthy();
|
||||
expect(gammaPost.post!.post.body).toBe("![](http://example.com/image2.png)");
|
||||
expect(betaPost.post.body).toBe("![](http://example.com/image2.png)");
|
||||
|
||||
// Make sure the alt text got federated
|
||||
expect(post.post_view.post.alt_text).toBe(gammaPost.post!.post.alt_text);
|
||||
expect(post.post_view.post.alt_text).toBe(betaPost.post.alt_text);
|
||||
});
|
||||
|
|
|
@ -55,7 +55,18 @@ afterAll(() => {
|
|||
unfollows();
|
||||
});
|
||||
|
||||
function assertPostFederation(postOne?: PostView, postTwo?: PostView) {
|
||||
async function assertPostFederation(postOne: PostView, postTwo: PostView) {
|
||||
// Link metadata is generated in background task and may not be ready yet at this time,
|
||||
// so wait for it explicitly. For removed posts we cant refetch anything.
|
||||
postOne = await waitForPost(beta, postOne.post, res => {
|
||||
return res === null || res?.post.embed_title !== null;
|
||||
});
|
||||
postTwo = await waitForPost(
|
||||
beta,
|
||||
postTwo.post,
|
||||
res => res === null || res?.post.embed_title !== null,
|
||||
);
|
||||
|
||||
expect(postOne?.post.ap_id).toBe(postTwo?.post.ap_id);
|
||||
expect(postOne?.post.name).toBe(postTwo?.post.name);
|
||||
expect(postOne?.post.body).toBe(postTwo?.post.body);
|
||||
|
@ -109,7 +120,7 @@ test("Create a post", async () => {
|
|||
expect(betaPost?.community.local).toBe(true);
|
||||
expect(betaPost?.creator.local).toBe(false);
|
||||
expect(betaPost?.counts.score).toBe(1);
|
||||
assertPostFederation(betaPost, postRes.post_view);
|
||||
await assertPostFederation(betaPost, postRes.post_view);
|
||||
|
||||
// Delta only follows beta, so it should not see an alpha ap_id
|
||||
await expect(
|
||||
|
@ -157,7 +168,7 @@ test("Unlike a post", async () => {
|
|||
expect(betaPost?.community.local).toBe(true);
|
||||
expect(betaPost?.creator.local).toBe(false);
|
||||
expect(betaPost?.counts.score).toBe(0);
|
||||
assertPostFederation(betaPost, postRes.post_view);
|
||||
await assertPostFederation(betaPost, postRes.post_view);
|
||||
});
|
||||
|
||||
test("Update a post", async () => {
|
||||
|
@ -178,7 +189,7 @@ test("Update a post", async () => {
|
|||
expect(betaPost.community.local).toBe(true);
|
||||
expect(betaPost.creator.local).toBe(false);
|
||||
expect(betaPost.post.name).toBe(updatedName);
|
||||
assertPostFederation(betaPost, updatedPost.post_view);
|
||||
await assertPostFederation(betaPost, updatedPost.post_view);
|
||||
|
||||
// Make sure lemmy beta cannot update the post
|
||||
await expect(editPost(beta, betaPost.post)).rejects.toStrictEqual(
|
||||
|
@ -329,7 +340,7 @@ test("Delete a post", async () => {
|
|||
throw "Missing beta post 2";
|
||||
}
|
||||
expect(betaPost2.post.deleted).toBe(false);
|
||||
assertPostFederation(betaPost2, undeletedPost.post_view);
|
||||
await assertPostFederation(betaPost2, undeletedPost.post_view);
|
||||
|
||||
// Make sure lemmy beta cannot delete the post
|
||||
await expect(deletePost(beta, true, betaPost2.post)).rejects.toStrictEqual(
|
||||
|
@ -372,7 +383,7 @@ test("Remove a post from admin and community on different instance", async () =>
|
|||
// Make sure lemmy beta sees post is undeleted
|
||||
let betaPost2 = (await resolvePost(beta, postRes.post_view.post)).post;
|
||||
expect(betaPost2?.post.removed).toBe(false);
|
||||
assertPostFederation(betaPost2, undeletedPost.post_view);
|
||||
await assertPostFederation(betaPost2!, undeletedPost.post_view);
|
||||
});
|
||||
|
||||
test("Remove a post from admin and community on same instance", async () => {
|
||||
|
@ -403,7 +414,7 @@ test("Remove a post from admin and community on same instance", async () => {
|
|||
p => p?.post_view.post.removed ?? false,
|
||||
);
|
||||
expect(alphaPost?.post_view.post.removed).toBe(true);
|
||||
assertPostFederation(alphaPost.post_view, removePostRes.post_view);
|
||||
await assertPostFederation(alphaPost.post_view, removePostRes.post_view);
|
||||
|
||||
// Undelete
|
||||
let undeletedPost = await removePost(beta, false, betaPost.post);
|
||||
|
@ -416,7 +427,7 @@ test("Remove a post from admin and community on same instance", async () => {
|
|||
p => !!p && !p.post.removed,
|
||||
);
|
||||
expect(alphaPost2.post.removed).toBe(false);
|
||||
assertPostFederation(alphaPost2, undeletedPost.post_view);
|
||||
await assertPostFederation(alphaPost2, undeletedPost.post_view);
|
||||
await unfollowRemotes(alpha);
|
||||
});
|
||||
|
||||
|
|
|
@ -19,8 +19,9 @@ import {
|
|||
getPost,
|
||||
getComments,
|
||||
fetchFunction,
|
||||
alphaImage,
|
||||
} from "./shared";
|
||||
import { LemmyHttp, SaveUserSettings } from "lemmy-js-client";
|
||||
import { LemmyHttp, SaveUserSettings, UploadImage } from "lemmy-js-client";
|
||||
import { GetPosts } from "lemmy-js-client/dist/types/GetPosts";
|
||||
|
||||
beforeAll(setupLogins);
|
||||
|
@ -159,3 +160,34 @@ test("Create user with accept-language", async () => {
|
|||
// which is automatically enabled by backend
|
||||
expect(langs).toStrictEqual(["und", "de", "en", "fr"]);
|
||||
});
|
||||
|
||||
test("Set a new avatar, old avatar is deleted", async () => {
|
||||
const listMediaRes = await alphaImage.listMedia();
|
||||
expect(listMediaRes.images.length).toBe(0);
|
||||
const upload_form1: UploadImage = {
|
||||
image: Buffer.from("test1"),
|
||||
};
|
||||
const upload1 = await alphaImage.uploadImage(upload_form1);
|
||||
expect(upload1.url).toBeDefined();
|
||||
|
||||
let form1 = {
|
||||
avatar: upload1.url,
|
||||
};
|
||||
await saveUserSettings(alpha, form1);
|
||||
const listMediaRes1 = await alphaImage.listMedia();
|
||||
expect(listMediaRes1.images.length).toBe(1);
|
||||
|
||||
const upload_form2: UploadImage = {
|
||||
image: Buffer.from("test2"),
|
||||
};
|
||||
const upload2 = await alphaImage.uploadImage(upload_form2);
|
||||
expect(upload2.url).toBeDefined();
|
||||
|
||||
let form2 = {
|
||||
avatar: upload1.url,
|
||||
};
|
||||
await saveUserSettings(alpha, form2);
|
||||
// make sure only the new avatar is kept
|
||||
const listMediaRes2 = await alphaImage.listMedia();
|
||||
expect(listMediaRes2.images.length).toBe(1);
|
||||
});
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
use actix_web::web::{Data, Json};
|
||||
use activitypub_federation::config::Data;
|
||||
use actix_web::web::Json;
|
||||
use lemmy_api_common::{
|
||||
context::LemmyContext,
|
||||
person::SaveUserSettings,
|
||||
request::replace_image,
|
||||
utils::{
|
||||
get_url_blocklist,
|
||||
local_site_to_slur_regex,
|
||||
|
@ -40,6 +42,8 @@ pub async fn save_user_settings(
|
|||
let bio = diesel_option_overwrite(
|
||||
process_markdown_opt(&data.bio, &slur_regex, &url_blocklist, &context).await?,
|
||||
);
|
||||
replace_image(&data.avatar, &local_user_view.person.avatar, &context).await?;
|
||||
replace_image(&data.banner, &local_user_view.person.banner, &context).await?;
|
||||
|
||||
let avatar = proxy_image_link_opt_api(&data.avatar, &context).await?;
|
||||
let banner = proxy_image_link_opt_api(&data.banner, &context).await?;
|
||||
|
|
|
@ -15,7 +15,7 @@ use lemmy_db_views::structs::{CustomEmojiView, LocalUserView, SiteView};
|
|||
use lemmy_db_views_actor::structs::PersonView;
|
||||
use lemmy_utils::{
|
||||
error::{LemmyError, LemmyErrorType},
|
||||
version,
|
||||
VERSION,
|
||||
};
|
||||
|
||||
#[tracing::instrument(skip(context))]
|
||||
|
@ -68,7 +68,7 @@ pub async fn leave_admin(
|
|||
Ok(Json(GetSiteResponse {
|
||||
site_view,
|
||||
admins,
|
||||
version: version::VERSION.to_string(),
|
||||
version: VERSION.to_string(),
|
||||
my_user: None,
|
||||
all_languages,
|
||||
discussion_languages,
|
||||
|
|
|
@ -3,15 +3,13 @@ use activitypub_federation::config::Data;
|
|||
use actix_web::web::Json;
|
||||
use lemmy_api_common::{
|
||||
context::LemmyContext,
|
||||
request::delete_image_from_pictrs,
|
||||
send_activity::{ActivityChannel, SendActivityData},
|
||||
site::PurgePerson,
|
||||
utils::is_admin,
|
||||
utils::{is_admin, purge_user_account},
|
||||
SuccessResponse,
|
||||
};
|
||||
use lemmy_db_schema::{
|
||||
source::{
|
||||
images::LocalImage,
|
||||
moderator::{AdminPurgePerson, AdminPurgePersonForm},
|
||||
person::{Person, PersonUpdateForm},
|
||||
},
|
||||
|
@ -29,18 +27,6 @@ pub async fn purge_person(
|
|||
// Only let admin purge an item
|
||||
is_admin(&local_user_view)?;
|
||||
|
||||
// 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 {
|
||||
let pictrs_uploads =
|
||||
LocalImage::get_all_by_local_user_id(&mut context.pool(), local_user.local_user.id).await?;
|
||||
|
||||
for upload in pictrs_uploads {
|
||||
delete_image_from_pictrs(&upload.pictrs_alias, &upload.pictrs_delete_token, &context)
|
||||
.await
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
|
||||
let person = Person::read(&mut context.pool(), data.person_id).await?;
|
||||
ban_nonlocal_user_from_local_communities(
|
||||
&local_user_view,
|
||||
|
@ -54,7 +40,8 @@ pub async fn purge_person(
|
|||
.await?;
|
||||
|
||||
// Clear profile data.
|
||||
Person::delete_account(&mut context.pool(), data.person_id).await?;
|
||||
purge_user_account(data.person_id, &context).await?;
|
||||
|
||||
// Keep person record, but mark as banned to prevent login or refetching from home instance.
|
||||
let person = Person::update(
|
||||
&mut context.pool(),
|
||||
|
|
|
@ -112,7 +112,7 @@ pub async fn send_local_notifs(
|
|||
if let Ok(mention_user_view) = user_view {
|
||||
// TODO
|
||||
// At some point, make it so you can't tag the parent creator either
|
||||
// This can cause two notifications, one for reply and the other for mention
|
||||
// Potential duplication of notifications, one for reply and the other for mention, is handled below by checking recipient ids
|
||||
recipient_ids.push(mention_user_view.local_user.id);
|
||||
|
||||
let user_mention_form = PersonMentionInsertForm {
|
||||
|
@ -163,30 +163,33 @@ pub async fn send_local_notifs(
|
|||
if parent_comment.creator_id != person.id && !check_blocks {
|
||||
let user_view = LocalUserView::read_person(&mut context.pool(), parent_creator_id).await;
|
||||
if let Ok(parent_user_view) = user_view {
|
||||
recipient_ids.push(parent_user_view.local_user.id);
|
||||
// Don't duplicate notif if already mentioned by checking recipient ids
|
||||
if !recipient_ids.contains(&parent_user_view.local_user.id) {
|
||||
recipient_ids.push(parent_user_view.local_user.id);
|
||||
|
||||
let comment_reply_form = CommentReplyInsertForm {
|
||||
recipient_id: parent_user_view.person.id,
|
||||
comment_id: comment.id,
|
||||
read: None,
|
||||
};
|
||||
let comment_reply_form = CommentReplyInsertForm {
|
||||
recipient_id: parent_user_view.person.id,
|
||||
comment_id: comment.id,
|
||||
read: None,
|
||||
};
|
||||
|
||||
// Allow this to fail softly, since comment edits might re-update or replace it
|
||||
// Let the uniqueness handle this fail
|
||||
CommentReply::create(&mut context.pool(), &comment_reply_form)
|
||||
.await
|
||||
.ok();
|
||||
// Allow this to fail softly, since comment edits might re-update or replace it
|
||||
// Let the uniqueness handle this fail
|
||||
CommentReply::create(&mut context.pool(), &comment_reply_form)
|
||||
.await
|
||||
.ok();
|
||||
|
||||
if do_send_email {
|
||||
let lang = get_interface_language(&parent_user_view);
|
||||
let content = markdown_to_html(&comment.content);
|
||||
send_email_to_user(
|
||||
&parent_user_view,
|
||||
&lang.notification_comment_reply_subject(&person.name),
|
||||
&lang.notification_comment_reply_body(&content, &inbox_link, &person.name),
|
||||
context.settings(),
|
||||
)
|
||||
.await
|
||||
if do_send_email {
|
||||
let lang = get_interface_language(&parent_user_view);
|
||||
let content = markdown_to_html(&comment.content);
|
||||
send_email_to_user(
|
||||
&parent_user_view,
|
||||
&lang.notification_comment_reply_subject(&person.name),
|
||||
&lang.notification_comment_reply_body(&content, &inbox_link, &person.name),
|
||||
context.settings(),
|
||||
)
|
||||
.await
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -205,30 +208,32 @@ pub async fn send_local_notifs(
|
|||
let creator_id = post.creator_id;
|
||||
let parent_user = LocalUserView::read_person(&mut context.pool(), creator_id).await;
|
||||
if let Ok(parent_user_view) = parent_user {
|
||||
recipient_ids.push(parent_user_view.local_user.id);
|
||||
if !recipient_ids.contains(&parent_user_view.local_user.id) {
|
||||
recipient_ids.push(parent_user_view.local_user.id);
|
||||
|
||||
let comment_reply_form = CommentReplyInsertForm {
|
||||
recipient_id: parent_user_view.person.id,
|
||||
comment_id: comment.id,
|
||||
read: None,
|
||||
};
|
||||
let comment_reply_form = CommentReplyInsertForm {
|
||||
recipient_id: parent_user_view.person.id,
|
||||
comment_id: comment.id,
|
||||
read: None,
|
||||
};
|
||||
|
||||
// Allow this to fail softly, since comment edits might re-update or replace it
|
||||
// Let the uniqueness handle this fail
|
||||
CommentReply::create(&mut context.pool(), &comment_reply_form)
|
||||
.await
|
||||
.ok();
|
||||
// Allow this to fail softly, since comment edits might re-update or replace it
|
||||
// Let the uniqueness handle this fail
|
||||
CommentReply::create(&mut context.pool(), &comment_reply_form)
|
||||
.await
|
||||
.ok();
|
||||
|
||||
if do_send_email {
|
||||
let lang = get_interface_language(&parent_user_view);
|
||||
let content = markdown_to_html(&comment.content);
|
||||
send_email_to_user(
|
||||
&parent_user_view,
|
||||
&lang.notification_post_reply_subject(&person.name),
|
||||
&lang.notification_post_reply_body(&content, &inbox_link, &person.name),
|
||||
context.settings(),
|
||||
)
|
||||
.await
|
||||
if do_send_email {
|
||||
let lang = get_interface_language(&parent_user_view);
|
||||
let content = markdown_to_html(&comment.content);
|
||||
send_email_to_user(
|
||||
&parent_user_view,
|
||||
&lang.notification_post_reply_subject(&person.name),
|
||||
&lang.notification_post_reply_body(&content, &inbox_link, &person.name),
|
||||
context.settings(),
|
||||
)
|
||||
.await
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,18 +1,26 @@
|
|||
use crate::{
|
||||
context::LemmyContext,
|
||||
lemmy_db_schema::traits::Crud,
|
||||
post::{LinkMetadata, OpenGraphData},
|
||||
utils::proxy_image_link,
|
||||
send_activity::{ActivityChannel, SendActivityData},
|
||||
utils::{local_site_opt_to_sensitive, proxy_image_link, proxy_image_link_opt_apub},
|
||||
};
|
||||
use activitypub_federation::config::Data;
|
||||
use encoding::{all::encodings, DecoderTrap};
|
||||
use lemmy_db_schema::{
|
||||
newtypes::DbUrl,
|
||||
source::images::{LocalImage, LocalImageForm},
|
||||
source::{
|
||||
images::{LocalImage, LocalImageForm},
|
||||
local_site::LocalSite,
|
||||
post::{Post, PostUpdateForm},
|
||||
},
|
||||
};
|
||||
use lemmy_utils::{
|
||||
error::{LemmyError, LemmyErrorType},
|
||||
settings::structs::{PictrsImageMode, Settings},
|
||||
version::VERSION,
|
||||
spawn_try_task,
|
||||
REQWEST_TIMEOUT,
|
||||
VERSION,
|
||||
};
|
||||
use mime::Mime;
|
||||
use reqwest::{header::CONTENT_TYPE, Client, ClientBuilder};
|
||||
|
@ -24,11 +32,7 @@ use urlencoding::encode;
|
|||
use webpage::HTML;
|
||||
|
||||
pub fn client_builder(settings: &Settings) -> ClientBuilder {
|
||||
let user_agent = format!(
|
||||
"Lemmy/{}; +{}",
|
||||
VERSION,
|
||||
settings.get_protocol_and_hostname()
|
||||
);
|
||||
let user_agent = format!("Lemmy/{VERSION}; +{}", settings.get_protocol_and_hostname());
|
||||
|
||||
Client::builder()
|
||||
.user_agent(user_agent.clone())
|
||||
|
@ -82,6 +86,50 @@ pub async fn fetch_link_metadata_opt(
|
|||
_ => Default::default(),
|
||||
}
|
||||
}
|
||||
/// Generate post thumbnail in background task, because some sites can be very slow to respond.
|
||||
///
|
||||
/// Takes a callback to generate a send activity task, so that post can be federated with metadata.
|
||||
pub fn generate_post_link_metadata(
|
||||
post: Post,
|
||||
custom_thumbnail: Option<Url>,
|
||||
send_activity: impl FnOnce(Post) -> Option<SendActivityData> + Send + 'static,
|
||||
local_site: Option<LocalSite>,
|
||||
context: Data<LemmyContext>,
|
||||
) {
|
||||
spawn_try_task(async move {
|
||||
let allow_sensitive = local_site_opt_to_sensitive(&local_site);
|
||||
let page_is_sensitive = post.nsfw;
|
||||
let allow_generate_thumbnail = allow_sensitive || !page_is_sensitive;
|
||||
let mut thumbnail_url = custom_thumbnail.or_else(|| post.thumbnail_url.map(Into::into));
|
||||
let do_generate_thumbnail = thumbnail_url.is_none() && allow_generate_thumbnail;
|
||||
|
||||
// Generate local thumbnail only if no thumbnail was federated and 'sensitive' attributes allow it.
|
||||
let metadata = fetch_link_metadata_opt(
|
||||
post.url.map(Into::into).as_ref(),
|
||||
do_generate_thumbnail,
|
||||
&context,
|
||||
)
|
||||
.await;
|
||||
if let Some(thumbnail_url_) = metadata.thumbnail {
|
||||
thumbnail_url = Some(thumbnail_url_.into());
|
||||
}
|
||||
let thumbnail_url = proxy_image_link_opt_apub(thumbnail_url, &context).await?;
|
||||
|
||||
let form = PostUpdateForm {
|
||||
embed_title: Some(metadata.opengraph_data.title),
|
||||
embed_description: Some(metadata.opengraph_data.description),
|
||||
embed_video_url: Some(metadata.opengraph_data.embed_video_url),
|
||||
thumbnail_url: Some(thumbnail_url),
|
||||
url_content_type: Some(metadata.content_type),
|
||||
..Default::default()
|
||||
};
|
||||
let updated_post = Post::update(&mut context.pool(), post.id, &form).await?;
|
||||
if let Some(send_activity) = send_activity(updated_post) {
|
||||
ActivityChannel::submit_activity(send_activity, &context).await?;
|
||||
}
|
||||
Ok(())
|
||||
});
|
||||
}
|
||||
|
||||
/// Extract site metadata from HTML Opengraph attributes.
|
||||
fn extract_opengraph_data(html_bytes: &[u8], url: &Url) -> Result<OpenGraphData, LemmyError> {
|
||||
|
@ -312,6 +360,26 @@ async fn is_image_content_type(client: &ClientWithMiddleware, url: &Url) -> Resu
|
|||
}
|
||||
}
|
||||
|
||||
/// When adding a new avatar or similar image, delete the old one.
|
||||
pub async fn replace_image(
|
||||
new_image: &Option<String>,
|
||||
old_image: &Option<DbUrl>,
|
||||
context: &Data<LemmyContext>,
|
||||
) -> Result<(), LemmyError> {
|
||||
if new_image.is_some() {
|
||||
// Ignore errors because image may be stored externally.
|
||||
if let Some(avatar) = &old_image {
|
||||
let image = LocalImage::delete_by_url(&mut context.pool(), avatar)
|
||||
.await
|
||||
.ok();
|
||||
if let Some(image) = image {
|
||||
delete_image_from_pictrs(&image.pictrs_alias, &image.pictrs_delete_token, context).await?;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[allow(clippy::unwrap_used)]
|
||||
#[allow(clippy::indexing_slicing)]
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use crate::{
|
||||
context::LemmyContext,
|
||||
request::purge_image_from_pictrs,
|
||||
request::{delete_image_from_pictrs, purge_image_from_pictrs},
|
||||
site::{FederatedInstances, InstanceWithFederationState},
|
||||
};
|
||||
use chrono::{DateTime, Days, Local, TimeZone, Utc};
|
||||
|
@ -12,7 +12,7 @@ use lemmy_db_schema::{
|
|||
community::{Community, CommunityModerator, CommunityUpdateForm},
|
||||
community_block::CommunityBlock,
|
||||
email_verification::{EmailVerification, EmailVerificationForm},
|
||||
images::RemoteImage,
|
||||
images::{LocalImage, RemoteImage},
|
||||
instance::Instance,
|
||||
instance_block::InstanceBlock,
|
||||
local_site::LocalSite,
|
||||
|
@ -660,6 +660,25 @@ pub async fn purge_image_posts_for_person(
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// Delete a local_user's images
|
||||
async fn delete_local_user_images(
|
||||
person_id: PersonId,
|
||||
context: &LemmyContext,
|
||||
) -> Result<(), LemmyError> {
|
||||
if let Ok(local_user) = LocalUserView::read_person(&mut context.pool(), person_id).await {
|
||||
let pictrs_uploads =
|
||||
LocalImage::get_all_by_local_user_id(&mut context.pool(), local_user.local_user.id).await?;
|
||||
|
||||
// Delete their images
|
||||
for upload in pictrs_uploads {
|
||||
delete_image_from_pictrs(&upload.pictrs_alias, &upload.pictrs_delete_token, context)
|
||||
.await
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn purge_image_posts_for_community(
|
||||
banned_community_id: CommunityId,
|
||||
context: &LemmyContext,
|
||||
|
@ -801,15 +820,22 @@ pub async fn purge_user_account(
|
|||
context: &LemmyContext,
|
||||
) -> Result<(), LemmyError> {
|
||||
let pool = &mut context.pool();
|
||||
// Delete their images
|
||||
|
||||
let person = Person::read(pool, person_id).await?;
|
||||
|
||||
// Delete their local images, if they're a local user
|
||||
delete_local_user_images(person_id, context).await.ok();
|
||||
|
||||
// No need to update avatar and banner, those are handled in Person::delete_account
|
||||
if let Some(avatar) = person.avatar {
|
||||
purge_image_from_pictrs(&avatar, context).await.ok();
|
||||
}
|
||||
if let Some(banner) = person.banner {
|
||||
purge_image_from_pictrs(&banner, context).await.ok();
|
||||
}
|
||||
// No need to update avatar and banner, those are handled in Person::delete_account
|
||||
|
||||
// Purge image posts
|
||||
purge_image_posts_for_person(person_id, context).await.ok();
|
||||
|
||||
// Comments
|
||||
Comment::permadelete_for_creator(pool, person_id)
|
||||
|
@ -821,9 +847,6 @@ pub async fn purge_user_account(
|
|||
.await
|
||||
.with_lemmy_type(LemmyErrorType::CouldntUpdatePost)?;
|
||||
|
||||
// Purge image posts
|
||||
purge_image_posts_for_person(person_id, context).await?;
|
||||
|
||||
// Leave communities they mod
|
||||
CommunityModerator::leave_all_communities(pool, person_id).await?;
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@ use lemmy_api_common::{
|
|||
build_response::build_community_response,
|
||||
community::{CommunityResponse, EditCommunity},
|
||||
context::LemmyContext,
|
||||
request::replace_image,
|
||||
send_activity::{ActivityChannel, SendActivityData},
|
||||
utils::{
|
||||
check_community_mod_action,
|
||||
|
@ -42,6 +43,9 @@ pub async fn update_community(
|
|||
let description =
|
||||
process_markdown_opt(&data.description, &slur_regex, &url_blocklist, &context).await?;
|
||||
is_valid_body_field(&data.description, false)?;
|
||||
let old_community = Community::read(&mut context.pool(), data.community_id).await?;
|
||||
replace_image(&data.icon, &old_community.icon, &context).await?;
|
||||
replace_image(&data.banner, &old_community.banner, &context).await?;
|
||||
|
||||
let description = diesel_option_overwrite(description);
|
||||
let icon = proxy_image_link_opt_api(&data.icon, &context).await?;
|
||||
|
|
|
@ -4,8 +4,8 @@ use lemmy_api_common::{
|
|||
build_response::build_post_response,
|
||||
context::LemmyContext,
|
||||
post::{CreatePost, PostResponse},
|
||||
request::fetch_link_metadata_opt,
|
||||
send_activity::{ActivityChannel, SendActivityData},
|
||||
request::generate_post_link_metadata,
|
||||
send_activity::SendActivityData,
|
||||
utils::{
|
||||
check_community_user_action,
|
||||
generate_local_apub_endpoint,
|
||||
|
@ -75,6 +75,7 @@ pub async fn create_post(
|
|||
is_url_blocked(&url, &url_blocklist)?;
|
||||
check_url_scheme(&url)?;
|
||||
check_url_scheme(&custom_thumbnail)?;
|
||||
let url = proxy_image_link_opt_apub(url, &context).await?;
|
||||
|
||||
check_community_user_action(
|
||||
&local_user_view.person,
|
||||
|
@ -98,18 +99,6 @@ pub async fn create_post(
|
|||
}
|
||||
}
|
||||
|
||||
// Only generate the thumbnail if there's no custom thumbnail provided,
|
||||
// otherwise it will save it in pictrs
|
||||
let generate_thumbnail = custom_thumbnail.is_none();
|
||||
|
||||
// Fetch post links and pictrs cached image
|
||||
let metadata = fetch_link_metadata_opt(url.as_ref(), generate_thumbnail, &context).await;
|
||||
let url = proxy_image_link_opt_apub(url, &context).await?;
|
||||
let thumbnail_url = proxy_image_link_opt_apub(custom_thumbnail, &context)
|
||||
.await?
|
||||
.map(Into::into)
|
||||
.or(metadata.thumbnail);
|
||||
|
||||
// Only need to check if language is allowed in case user set it explicitly. When using default
|
||||
// language, it already only returns allowed languages.
|
||||
CommunityLanguage::is_allowed_community_language(
|
||||
|
@ -134,18 +123,13 @@ pub async fn create_post(
|
|||
|
||||
let post_form = PostInsertForm::builder()
|
||||
.name(data.name.trim().to_string())
|
||||
.url_content_type(metadata.content_type)
|
||||
.url(url)
|
||||
.body(body)
|
||||
.alt_text(data.alt_text.clone())
|
||||
.community_id(data.community_id)
|
||||
.creator_id(local_user_view.person.id)
|
||||
.nsfw(data.nsfw)
|
||||
.embed_title(metadata.opengraph_data.title)
|
||||
.embed_description(metadata.opengraph_data.description)
|
||||
.embed_video_url(metadata.opengraph_data.embed_video_url)
|
||||
.language_id(language_id)
|
||||
.thumbnail_url(thumbnail_url)
|
||||
.build();
|
||||
|
||||
let inserted_post = Post::create(&mut context.pool(), &post_form)
|
||||
|
@ -170,6 +154,14 @@ pub async fn create_post(
|
|||
.await
|
||||
.with_lemmy_type(LemmyErrorType::CouldntCreatePost)?;
|
||||
|
||||
generate_post_link_metadata(
|
||||
updated_post.clone(),
|
||||
custom_thumbnail,
|
||||
|post| Some(SendActivityData::CreatePost(post)),
|
||||
Some(local_site),
|
||||
context.reset_request_count(),
|
||||
);
|
||||
|
||||
// They like their own post by default
|
||||
let person_id = local_user_view.person.id;
|
||||
let post_id = inserted_post.id;
|
||||
|
@ -183,9 +175,6 @@ pub async fn create_post(
|
|||
.await
|
||||
.with_lemmy_type(LemmyErrorType::CouldntLikePost)?;
|
||||
|
||||
ActivityChannel::submit_activity(SendActivityData::CreatePost(updated_post.clone()), &context)
|
||||
.await?;
|
||||
|
||||
// Mark the post as read
|
||||
mark_post_as_read(person_id, post_id, &mut context.pool()).await?;
|
||||
|
||||
|
|
|
@ -4,8 +4,8 @@ use lemmy_api_common::{
|
|||
build_response::build_post_response,
|
||||
context::LemmyContext,
|
||||
post::{EditPost, PostResponse},
|
||||
request::fetch_link_metadata,
|
||||
send_activity::{ActivityChannel, SendActivityData},
|
||||
request::generate_post_link_metadata,
|
||||
send_activity::SendActivityData,
|
||||
utils::{
|
||||
check_community_user_action,
|
||||
get_url_blocklist,
|
||||
|
@ -84,40 +84,11 @@ pub async fn update_post(
|
|||
Err(LemmyErrorType::NoPostEditAllowed)?
|
||||
}
|
||||
|
||||
// Fetch post links and thumbnail if url was updated
|
||||
let (embed_title, embed_description, embed_video_url, metadata_thumbnail, metadata_content_type) =
|
||||
match &url {
|
||||
Some(url) => {
|
||||
// Only generate the thumbnail if there's no custom thumbnail provided,
|
||||
// otherwise it will save it in pictrs
|
||||
let generate_thumbnail = custom_thumbnail.is_none() || orig_post.thumbnail_url.is_none();
|
||||
|
||||
let metadata = fetch_link_metadata(url, generate_thumbnail, &context).await?;
|
||||
(
|
||||
Some(metadata.opengraph_data.title),
|
||||
Some(metadata.opengraph_data.description),
|
||||
Some(metadata.opengraph_data.embed_video_url),
|
||||
Some(metadata.thumbnail),
|
||||
Some(metadata.content_type),
|
||||
)
|
||||
}
|
||||
_ => Default::default(),
|
||||
};
|
||||
|
||||
let url = match url {
|
||||
Some(url) => Some(proxy_image_link_opt_apub(Some(url), &context).await?),
|
||||
_ => Default::default(),
|
||||
};
|
||||
|
||||
let custom_thumbnail = match custom_thumbnail {
|
||||
Some(custom_thumbnail) => {
|
||||
Some(proxy_image_link_opt_apub(Some(custom_thumbnail), &context).await?)
|
||||
}
|
||||
_ => Default::default(),
|
||||
};
|
||||
|
||||
let thumbnail_url = custom_thumbnail.or(metadata_thumbnail);
|
||||
|
||||
let language_id = data.language_id;
|
||||
CommunityLanguage::is_allowed_community_language(
|
||||
&mut context.pool(),
|
||||
|
@ -129,15 +100,10 @@ pub async fn update_post(
|
|||
let post_form = PostUpdateForm {
|
||||
name: data.name.clone(),
|
||||
url,
|
||||
url_content_type: metadata_content_type,
|
||||
body: diesel_option_overwrite(body),
|
||||
alt_text: diesel_option_overwrite(data.alt_text.clone()),
|
||||
nsfw: data.nsfw,
|
||||
embed_title,
|
||||
embed_description,
|
||||
embed_video_url,
|
||||
language_id: data.language_id,
|
||||
thumbnail_url,
|
||||
updated: Some(Some(naive_now())),
|
||||
..Default::default()
|
||||
};
|
||||
|
@ -147,7 +113,13 @@ pub async fn update_post(
|
|||
.await
|
||||
.with_lemmy_type(LemmyErrorType::CouldntUpdatePost)?;
|
||||
|
||||
ActivityChannel::submit_activity(SendActivityData::UpdatePost(updated_post), &context).await?;
|
||||
generate_post_link_metadata(
|
||||
updated_post.clone(),
|
||||
custom_thumbnail,
|
||||
|post| Some(SendActivityData::UpdatePost(post)),
|
||||
Some(local_site),
|
||||
context.reset_request_count(),
|
||||
);
|
||||
|
||||
build_post_response(
|
||||
context.deref(),
|
||||
|
|
|
@ -20,8 +20,8 @@ use lemmy_db_views_actor::structs::{
|
|||
};
|
||||
use lemmy_utils::{
|
||||
error::{LemmyError, LemmyErrorExt, LemmyErrorType},
|
||||
version,
|
||||
CACHE_DURATION_SHORT,
|
||||
VERSION,
|
||||
};
|
||||
use moka::future::Cache;
|
||||
use once_cell::sync::Lazy;
|
||||
|
@ -52,7 +52,7 @@ pub async fn get_site(
|
|||
Ok(GetSiteResponse {
|
||||
site_view,
|
||||
admins,
|
||||
version: version::VERSION.to_string(),
|
||||
version: VERSION.to_string(),
|
||||
my_user: None,
|
||||
all_languages,
|
||||
discussion_languages,
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
use crate::site::{application_question_check, site_default_post_listing_type_check};
|
||||
use actix_web::web::{Data, Json};
|
||||
use activitypub_federation::config::Data;
|
||||
use actix_web::web::Json;
|
||||
use lemmy_api_common::{
|
||||
context::LemmyContext,
|
||||
request::replace_image,
|
||||
site::{EditSite, SiteResponse},
|
||||
utils::{
|
||||
get_url_blocklist,
|
||||
|
@ -63,6 +65,9 @@ pub async fn update_site(
|
|||
SiteLanguage::update(&mut context.pool(), discussion_languages.clone(), &site).await?;
|
||||
}
|
||||
|
||||
replace_image(&data.icon, &site.icon, &context).await?;
|
||||
replace_image(&data.banner, &site.banner, &context).await?;
|
||||
|
||||
let slur_regex = local_site_to_slur_regex(&local_site);
|
||||
let url_blocklist = get_url_blocklist(&context).await?;
|
||||
let sidebar = process_markdown_opt(&data.sidebar, &slur_regex, &url_blocklist, &context).await?;
|
||||
|
|
|
@ -24,10 +24,9 @@ use chrono::{DateTime, Utc};
|
|||
use html2text::{from_read_with_decorator, render::text_renderer::TrivialDecorator};
|
||||
use lemmy_api_common::{
|
||||
context::LemmyContext,
|
||||
request::fetch_link_metadata_opt,
|
||||
request::generate_post_link_metadata,
|
||||
utils::{
|
||||
get_url_blocklist,
|
||||
local_site_opt_to_sensitive,
|
||||
local_site_opt_to_slur_regex,
|
||||
process_markdown_opt,
|
||||
proxy_image_link_opt_apub,
|
||||
|
@ -218,6 +217,7 @@ impl Object for ApubPost {
|
|||
let old_post = page.id.dereference_local(context).await;
|
||||
|
||||
let first_attachment = page.attachment.first();
|
||||
let local_site = LocalSite::read(&mut context.pool()).await.ok();
|
||||
|
||||
let form = if !page.is_mod_action(context).await? {
|
||||
let url = if let Some(attachment) = first_attachment.cloned() {
|
||||
|
@ -231,20 +231,8 @@ impl Object for ApubPost {
|
|||
check_url_scheme(&url)?;
|
||||
|
||||
let alt_text = first_attachment.cloned().and_then(Attachment::alt_text);
|
||||
let local_site = LocalSite::read(&mut context.pool()).await.ok();
|
||||
let allow_sensitive = local_site_opt_to_sensitive(&local_site);
|
||||
let page_is_sensitive = page.sensitive.unwrap_or(false);
|
||||
let allow_generate_thumbnail = allow_sensitive || !page_is_sensitive;
|
||||
let mut thumbnail_url = page.image.map(|i| i.url);
|
||||
let do_generate_thumbnail = thumbnail_url.is_none() && allow_generate_thumbnail;
|
||||
|
||||
// Generate local thumbnail only if no thumbnail was federated and 'sensitive' attributes allow it.
|
||||
let metadata = fetch_link_metadata_opt(url.as_ref(), do_generate_thumbnail, context).await;
|
||||
if let Some(thumbnail_url_) = metadata.thumbnail {
|
||||
thumbnail_url = Some(thumbnail_url_.into());
|
||||
}
|
||||
let url = proxy_image_link_opt_apub(url, context).await?;
|
||||
let thumbnail_url = proxy_image_link_opt_apub(thumbnail_url, context).await?;
|
||||
|
||||
let slur_regex = &local_site_opt_to_slur_regex(&local_site);
|
||||
let url_blocklist = get_url_blocklist(context).await?;
|
||||
|
@ -254,30 +242,22 @@ impl Object for ApubPost {
|
|||
let language_id =
|
||||
LanguageTag::to_language_id_single(page.language, &mut context.pool()).await?;
|
||||
|
||||
PostInsertForm {
|
||||
name,
|
||||
url: url.map(Into::into),
|
||||
body,
|
||||
alt_text,
|
||||
creator_id: creator.id,
|
||||
community_id: community.id,
|
||||
removed: None,
|
||||
locked: page.comments_enabled.map(|e| !e),
|
||||
published: page.published.map(Into::into),
|
||||
updated: page.updated.map(Into::into),
|
||||
deleted: Some(false),
|
||||
nsfw: page.sensitive,
|
||||
embed_title: metadata.opengraph_data.title,
|
||||
embed_description: metadata.opengraph_data.description,
|
||||
embed_video_url: metadata.opengraph_data.embed_video_url,
|
||||
thumbnail_url,
|
||||
ap_id: Some(page.id.clone().into()),
|
||||
local: Some(false),
|
||||
language_id,
|
||||
featured_community: None,
|
||||
featured_local: None,
|
||||
url_content_type: metadata.content_type,
|
||||
}
|
||||
PostInsertForm::builder()
|
||||
.name(name)
|
||||
.url(url.map(Into::into))
|
||||
.body(body)
|
||||
.alt_text(alt_text)
|
||||
.creator_id(creator.id)
|
||||
.community_id(community.id)
|
||||
.locked(page.comments_enabled.map(|e| !e))
|
||||
.published(page.published.map(Into::into))
|
||||
.updated(page.updated.map(Into::into))
|
||||
.deleted(Some(false))
|
||||
.nsfw(page.sensitive)
|
||||
.ap_id(Some(page.id.clone().into()))
|
||||
.local(Some(false))
|
||||
.language_id(language_id)
|
||||
.build()
|
||||
} else {
|
||||
// if is mod action, only update locked/stickied fields, nothing else
|
||||
PostInsertForm::builder()
|
||||
|
@ -292,6 +272,14 @@ impl Object for ApubPost {
|
|||
|
||||
let post = Post::create(&mut context.pool(), &form).await?;
|
||||
|
||||
generate_post_link_metadata(
|
||||
post.clone(),
|
||||
page.image.map(|i| i.url),
|
||||
|_| None,
|
||||
local_site,
|
||||
context.reset_request_count(),
|
||||
);
|
||||
|
||||
// write mod log entry for lock
|
||||
if Page::is_locked_changed(&old_post, &page.comments_enabled) {
|
||||
let form = ModLockPostForm {
|
||||
|
|
|
@ -29,6 +29,7 @@ use diesel::{
|
|||
select,
|
||||
sql_types,
|
||||
update,
|
||||
BoolExpressionMethods,
|
||||
ExpressionMethods,
|
||||
NullableExpressionMethods,
|
||||
QueryDsl,
|
||||
|
@ -150,30 +151,16 @@ impl Community {
|
|||
for p in &posts {
|
||||
debug_assert!(p.community_id == community_id);
|
||||
}
|
||||
conn
|
||||
.build_transaction()
|
||||
.run(|conn| {
|
||||
Box::pin(async move {
|
||||
update(
|
||||
// first remove all existing featured posts
|
||||
post::table,
|
||||
)
|
||||
.filter(post::dsl::community_id.eq(community_id))
|
||||
.set(post::dsl::featured_community.eq(false))
|
||||
.execute(conn)
|
||||
.await?;
|
||||
|
||||
// then mark the given posts as featured
|
||||
let post_ids: Vec<_> = posts.iter().map(|p| p.id).collect();
|
||||
update(post::table)
|
||||
.filter(post::dsl::id.eq_any(post_ids))
|
||||
.set(post::dsl::featured_community.eq(true))
|
||||
.execute(conn)
|
||||
.await?;
|
||||
Ok(())
|
||||
}) as _
|
||||
})
|
||||
.await
|
||||
// Mark the given posts as featured and all other posts as not featured.
|
||||
let post_ids = posts.iter().map(|p| p.id);
|
||||
update(post::table)
|
||||
.filter(post::dsl::community_id.eq(community_id))
|
||||
// This filter is just for performance
|
||||
.filter(post::dsl::featured_community.or(post::dsl::id.eq_any(post_ids.clone())))
|
||||
.set(post::dsl::featured_community.eq(post::dsl::id.eq_any(post_ids)))
|
||||
.execute(conn)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -74,12 +74,17 @@ impl LocalImage {
|
|||
query.load::<LocalImage>(conn).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<Self, Error> {
|
||||
let conn = &mut get_conn(pool).await?;
|
||||
diesel::delete(local_image::table.filter(local_image::pictrs_alias.eq(alias)))
|
||||
.execute(conn)
|
||||
.get_result(conn)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn delete_by_url(pool: &mut DbPool<'_>, url: &DbUrl) -> Result<Self, Error> {
|
||||
let alias = url.as_str().split('/').last().ok_or(NotFound)?;
|
||||
Self::delete_by_alias(pool, alias).await
|
||||
}
|
||||
}
|
||||
|
||||
impl RemoteImage {
|
||||
|
|
|
@ -49,14 +49,18 @@ fn queries<'a>() -> Queries<
|
|||
let list = move |mut conn: DbConn<'a>, options: PrivateMessageReportQuery| async move {
|
||||
let mut query = all_joins(private_message_report::table.into_boxed());
|
||||
|
||||
// If viewing all reports, order by newest, but if viewing unresolved only, show the oldest first (FIFO)
|
||||
if options.unresolved_only {
|
||||
query = query.filter(private_message_report::resolved.eq(false));
|
||||
query = query
|
||||
.filter(private_message_report::resolved.eq(false))
|
||||
.order_by(private_message_report::published.asc());
|
||||
} else {
|
||||
query = query.order_by(private_message_report::published.desc());
|
||||
}
|
||||
|
||||
let (limit, offset) = limit_and_offset(options.page, options.limit)?;
|
||||
|
||||
query
|
||||
.order_by(private_message::published.asc())
|
||||
.limit(limit)
|
||||
.offset(offset)
|
||||
.load::<PrivateMessageReportView>(&mut conn)
|
||||
|
|
|
@ -6,7 +6,7 @@ use lemmy_db_views::structs::SiteView;
|
|||
use lemmy_utils::{
|
||||
cache_header::{cache_1hour, cache_3days},
|
||||
error::LemmyError,
|
||||
version,
|
||||
VERSION,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use url::Url;
|
||||
|
@ -56,7 +56,7 @@ async fn node_info(context: web::Data<LemmyContext>) -> Result<HttpResponse, Err
|
|||
version: Some("2.0".to_string()),
|
||||
software: Some(NodeInfoSoftware {
|
||||
name: Some("lemmy".to_string()),
|
||||
version: Some(version::VERSION.to_string()),
|
||||
version: Some(VERSION.to_string()),
|
||||
}),
|
||||
protocols,
|
||||
usage: Some(NodeInfoUsage {
|
||||
|
|
|
@ -10,7 +10,6 @@ cfg_if! {
|
|||
pub mod response;
|
||||
pub mod settings;
|
||||
pub mod utils;
|
||||
pub mod version;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -20,6 +19,8 @@ use std::time::Duration;
|
|||
|
||||
pub type ConnectionId = usize;
|
||||
|
||||
pub const VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||
|
||||
pub const REQWEST_TIMEOUT: Duration = Duration::from_secs(10);
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
pub const VERSION: &str = "unknown version";
|
|
@ -28,7 +28,6 @@ COPY . ./
|
|||
# Debug build
|
||||
RUN --mount=type=cache,target=/lemmy/target set -ex; \
|
||||
if [ "${RUST_RELEASE_MODE}" = "debug" ]; then \
|
||||
echo "pub const VERSION: &str = \"$(git describe --tag)\";" > crates/utils/src/version.rs; \
|
||||
cargo build --features "${CARGO_BUILD_FEATURES}"; \
|
||||
mv target/"${RUST_RELEASE_MODE}"/lemmy_server ./lemmy_server; \
|
||||
fi
|
||||
|
@ -36,7 +35,6 @@ RUN --mount=type=cache,target=/lemmy/target set -ex; \
|
|||
# Release build
|
||||
RUN --mount=type=cache,target=/lemmy/target set -ex; \
|
||||
if [ "${RUST_RELEASE_MODE}" = "release" ]; then \
|
||||
echo "pub const VERSION: &str = \"$(git describe --tag)\";" > crates/utils/src/version.rs; \
|
||||
[ -z "$USE_RELEASE_CACHE" ] && cargo clean --release; \
|
||||
cargo build --features "${CARGO_BUILD_FEATURES}" --release; \
|
||||
mv target/"${RUST_RELEASE_MODE}"/lemmy_server ./lemmy_server; \
|
||||
|
@ -63,7 +61,6 @@ ENV RUST_RELEASE_MODE=${RUST_RELEASE_MODE} \
|
|||
# Debug build
|
||||
RUN --mount=type=cache,target=./target,uid=10001,gid=10001 set -ex; \
|
||||
if [ "${RUST_RELEASE_MODE}" = "debug" ]; then \
|
||||
echo "pub const VERSION: &str = \"$(git describe --tag)\";" > crates/utils/src/version.rs; \
|
||||
cargo build --features "${CARGO_BUILD_FEATURES}"; \
|
||||
mv "./target/$CARGO_BUILD_TARGET/$RUST_RELEASE_MODE/lemmy_server" /home/lemmy/lemmy_server; \
|
||||
fi
|
||||
|
@ -71,7 +68,6 @@ RUN --mount=type=cache,target=./target,uid=10001,gid=10001 set -ex; \
|
|||
# Release build
|
||||
RUN --mount=type=cache,target=./target,uid=10001,gid=10001 set -ex; \
|
||||
if [ "${RUST_RELEASE_MODE}" = "release" ]; then \
|
||||
echo "pub const VERSION: &str = \"$(git describe --tag)\";" > crates/utils/src/version.rs; \
|
||||
[ -z "$USE_RELEASE_CACHE" ] && cargo clean --release; \
|
||||
cargo build --features "${CARGO_BUILD_FEATURES}" --release; \
|
||||
mv "./target/$CARGO_BUILD_TARGET/$RUST_RELEASE_MODE/lemmy_server" /home/lemmy/lemmy_server; \
|
||||
|
|
|
@ -49,7 +49,7 @@ use lemmy_utils::{
|
|||
rate_limit::RateLimitCell,
|
||||
response::jsonify_plain_text_errors,
|
||||
settings::{structs::Settings, SETTINGS},
|
||||
version,
|
||||
VERSION,
|
||||
};
|
||||
use prometheus::default_registry;
|
||||
use prometheus_metrics::serve_prometheus;
|
||||
|
@ -109,7 +109,7 @@ pub struct CmdArgs {
|
|||
/// Placing the main function in lib.rs allows other crates to import it and embed Lemmy
|
||||
pub async fn start_lemmy_server(args: CmdArgs) -> Result<(), LemmyError> {
|
||||
// Print version number to log
|
||||
println!("Lemmy v{}", version::VERSION);
|
||||
println!("Lemmy v{VERSION}");
|
||||
|
||||
// return error 503 while running db migrations and startup tasks
|
||||
let mut startup_server_handle = None;
|
||||
|
|
Loading…
Reference in a new issue