Merge branch 'main' into drone-release-main

This commit is contained in:
Felix Ableitner 2021-04-08 14:24:41 +02:00
commit 3a6e0d9393
68 changed files with 395 additions and 315 deletions

View File

@ -1,3 +1,10 @@
# Lemmy v0.10.3 Release (2021-04-07)
- Fixing instances page.
- Fixed unban not working.
- Fixed post title fetching and cross-post search.
- Fixed navigating to a user page.
# Lemmy v0.10.2 Release (2021-04-05)
- Forcing a crash if config.hjson fails to load. Should show errors easier.

View File

@ -1 +1 @@
0.10.2
0.10.3

View File

@ -16,7 +16,7 @@
"eslint": "^7.18.0",
"eslint-plugin-jane": "^9.0.3",
"jest": "^26.6.3",
"lemmy-js-client": "0.10.0-rc.13",
"lemmy-js-client": "0.11.0-rc.3",
"node-fetch": "^2.6.1",
"prettier": "^2.1.2",
"ts-jest": "^26.4.4",

View File

@ -31,7 +31,6 @@ function assertCommunityFederation(
expect(communityOne.community.published).toBe(
communityTwo.community.published
);
expect(communityOne.creator.actor_id).toBe(communityTwo.creator.actor_id);
expect(communityOne.community.nsfw).toBe(communityTwo.community.nsfw);
expect(communityOne.community.removed).toBe(communityTwo.community.removed);
expect(communityOne.community.deleted).toBe(communityTwo.community.deleted);

View File

@ -15,8 +15,8 @@ let recipient_id: number;
beforeAll(async () => {
await setupLogins();
let follow = await followBeta(alpha);
recipient_id = follow.community_view.creator.id;
await followBeta(alpha);
recipient_id = 3;
});
afterAll(async () => {

View File

@ -3233,10 +3233,10 @@ language-tags@^1.0.5:
dependencies:
language-subtag-registry "~0.3.2"
lemmy-js-client@0.10.0-rc.13:
version "0.10.0-rc.13"
resolved "https://registry.yarnpkg.com/lemmy-js-client/-/lemmy-js-client-0.10.0-rc.13.tgz#ea2e88857243374d7fbd49ee6b4bb94c34359d85"
integrity sha512-zodvYkwBYR7iP27ah6L/QPUphUUdq38kCH7QF2CUYBrsSAEkGmq2kdz+iusnQ1Ht7Ad80GtYycFprsZBveV5eQ==
lemmy-js-client@0.11.0-rc.3:
version "0.11.0-rc.3"
resolved "https://registry.yarnpkg.com/lemmy-js-client/-/lemmy-js-client-0.11.0-rc.3.tgz#dd4727ca4d16fe9593368725aacfd9e7a8d52548"
integrity sha512-16mgl+TS1+0UHiY+ZKPuqHfbrn93h8K8tJ+kKJ1pjT2WhG23o0B8dLahG1jDQPG+dkXpR6PZxYudMYjuO8WvjQ==
leven@^3.1.0:
version "3.1.0"

View File

@ -10,11 +10,7 @@ use lemmy_api_common::{
};
use lemmy_apub::{ActorType, CommunityType, UserType};
use lemmy_db_queries::{
source::{
comment::Comment_,
community::{CommunityModerator_, Community_},
post::Post_,
},
source::{comment::Comment_, community::CommunityModerator_, post::Post_},
Bannable,
Crud,
Followable,
@ -324,12 +320,6 @@ impl Perform for TransferCommunity {
let data: &TransferCommunity = &self;
let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?;
let community_id = data.community_id;
let read_community = blocking(context.pool(), move |conn| {
Community::read(conn, community_id)
})
.await??;
let site_creator_id = blocking(context.pool(), move |conn| {
Site::read(conn, 1).map(|s| s.creator_id)
})
@ -337,7 +327,7 @@ impl Perform for TransferCommunity {
let mut admins = blocking(context.pool(), move |conn| PersonViewSafe::admins(conn)).await??;
// Making sure the creator, if an admin, is at the top
// Making sure the site creator, if an admin, is at the top
let creator_index = admins
.iter()
.position(|r| r.person.id == site_creator_id)
@ -345,8 +335,15 @@ impl Perform for TransferCommunity {
let creator_person = admins.remove(creator_index);
admins.insert(0, creator_person);
// Make sure user is the creator, or an admin
if local_user_view.person.id != read_community.creator_id
// Fetch the community mods
let community_id = data.community_id;
let mut community_mods = blocking(context.pool(), move |conn| {
CommunityModeratorView::for_community(conn, community_id)
})
.await??;
// Make sure transferrer is either the top community mod, or an admin
if local_user_view.person.id != community_mods[0].moderator.id
&& !admins
.iter()
.map(|a| a.person.id)
@ -355,19 +352,8 @@ impl Perform for TransferCommunity {
return Err(ApiError::err("not_an_admin").into());
}
let community_id = data.community_id;
let new_creator = data.person_id;
let update = move |conn: &'_ _| Community::update_creator(conn, community_id, new_creator);
if blocking(context.pool(), update).await?.is_err() {
return Err(ApiError::err("couldnt_update_community").into());
};
// You also have to re-do the community_moderator table, reordering it.
let community_id = data.community_id;
let mut community_mods = blocking(context.pool(), move |conn| {
CommunityModeratorView::for_community(conn, community_id)
})
.await??;
// You have to re-do the community_moderator table, reordering it.
// Add the transferee to the top
let creator_index = community_mods
.iter()
.position(|r| r.moderator.id == data.person_id)
@ -375,6 +361,7 @@ impl Perform for TransferCommunity {
let creator_person = community_mods.remove(creator_index);
community_mods.insert(0, creator_person);
// Delete all the mods
let community_id = data.community_id;
blocking(context.pool(), move |conn| {
CommunityModerator::delete_for_community(conn, community_id)
@ -382,6 +369,7 @@ impl Perform for TransferCommunity {
.await??;
// TODO: this should probably be a bulk operation
// Re-add the mods, in the new order
for cmod in &community_mods {
let community_moderator_form = CommunityModeratorForm {
community_id: cmod.community.id,
@ -395,6 +383,8 @@ impl Perform for TransferCommunity {
}
// Mod tables
// TODO there should probably be another table for transfer community
// Right now, it will just look like it modded them twice
let form = ModAddCommunityForm {
mod_person_id: local_user_view.person.id,
other_person_id: data.person_id,

View File

@ -1,9 +1,9 @@
use actix_web::{web, web::Data};
use captcha::Captcha;
use lemmy_api_common::{comment::*, community::*, person::*, post::*, site::*, websocket::*};
use lemmy_utils::{ConnectionId, LemmyError};
use lemmy_websocket::{serialize_websocket_message, LemmyContext, UserOperation};
use serde::Deserialize;
use std::{env, process::Command};
mod comment;
mod comment_report;
@ -63,6 +63,9 @@ pub async fn match_websocket_operation(
UserOperation::SaveUserSettings => {
do_websocket_operation::<SaveUserSettings>(context, id, op, data).await
}
UserOperation::ChangePassword => {
do_websocket_operation::<ChangePassword>(context, id, op, data).await
}
UserOperation::GetReportCount => {
do_websocket_operation::<GetReportCount>(context, id, op, data).await
}
@ -158,60 +161,23 @@ where
serialize_websocket_message(&op, &res)
}
pub(crate) fn captcha_espeak_wav_base64(captcha: &str) -> Result<String, LemmyError> {
let mut built_text = String::new();
/// Converts the captcha to a base64 encoded wav audio file
pub(crate) fn captcha_as_wav_base64(captcha: &Captcha) -> String {
let letters = captcha.as_wav();
// Building proper speech text for espeak
for mut c in captcha.chars() {
let new_str = if c.is_alphabetic() {
if c.is_lowercase() {
c.make_ascii_uppercase();
format!("lower case {} ... ", c)
} else {
c.make_ascii_uppercase();
format!("capital {} ... ", c)
}
} else {
format!("{} ...", c)
};
let mut concat_letters: Vec<u8> = Vec::new();
built_text.push_str(&new_str);
for letter in letters {
let bytes = letter.unwrap_or_default();
concat_letters.extend(bytes);
}
espeak_wav_base64(&built_text)
}
pub(crate) fn espeak_wav_base64(text: &str) -> Result<String, LemmyError> {
// Make a temp file path
let uuid = uuid::Uuid::new_v4().to_string();
let file_path = format!(
"{}/lemmy_espeak_{}.wav",
env::temp_dir().to_string_lossy(),
&uuid
);
// Write the wav file
Command::new("espeak")
.arg("-w")
.arg(&file_path)
.arg(text)
.status()?;
// Read the wav file bytes
let bytes = std::fs::read(&file_path)?;
// Delete the file
std::fs::remove_file(file_path)?;
// Convert to base64
let base64 = base64::encode(bytes);
Ok(base64)
base64::encode(concat_letters)
}
#[cfg(test)]
mod tests {
use crate::captcha_espeak_wav_base64;
use lemmy_api_common::check_validator_time;
use lemmy_db_queries::{establish_unpooled_connection, source::local_user::LocalUser_, Crud};
use lemmy_db_schema::source::{
@ -253,9 +219,4 @@ mod tests {
let num_deleted = Person::delete(&conn, inserted_person.id).unwrap();
assert_eq!(1, num_deleted);
}
#[test]
fn test_espeak() {
assert!(captcha_espeak_wav_base64("WxRt2l").is_ok())
}
}

View File

@ -1,4 +1,4 @@
use crate::{captcha_espeak_wav_base64, Perform};
use crate::{captcha_as_wav_base64, Perform};
use actix_web::web::Data;
use anyhow::Context;
use bcrypt::verify;
@ -18,7 +18,6 @@ use lemmy_db_queries::{
diesel_option_overwrite_to_url,
source::{
comment::Comment_,
community::Community_,
local_user::LocalUser_,
password_reset_request::PasswordResetRequest_,
person::Person_,
@ -33,7 +32,6 @@ use lemmy_db_schema::{
naive_now,
source::{
comment::Comment,
community::*,
local_user::{LocalUser, LocalUserForm},
moderator::*,
password_reset_request::*,
@ -60,7 +58,7 @@ use lemmy_utils::{
email::send_email,
location_info,
settings::structs::Settings,
utils::{generate_random_string, is_valid_preferred_username, naive_from_unix},
utils::{generate_random_string, is_valid_display_name, is_valid_matrix_id, naive_from_unix},
ApiError,
ConnectionId,
LemmyError,
@ -135,13 +133,11 @@ impl Perform for GetCaptcha {
let answer = captcha.chars_as_string();
let png_byte_array = captcha.as_png().expect("failed to generate captcha");
let png = base64::encode(png_byte_array);
let png = captcha.as_base64().expect("failed to generate captcha");
let uuid = uuid::Uuid::new_v4().to_string();
let wav = captcha_espeak_wav_base64(&answer).ok();
let wav = captcha_as_wav_base64(&captcha);
let captcha_item = CaptchaItem {
answer,
@ -174,7 +170,7 @@ impl Perform for SaveUserSettings {
let banner = diesel_option_overwrite_to_url(&data.banner)?;
let email = diesel_option_overwrite(&data.email);
let bio = diesel_option_overwrite(&data.bio);
let preferred_username = diesel_option_overwrite(&data.preferred_username);
let display_name = diesel_option_overwrite(&data.display_name);
let matrix_user_id = diesel_option_overwrite(&data.matrix_user_id);
if let Some(Some(bio)) = &bio {
@ -183,59 +179,30 @@ impl Perform for SaveUserSettings {
}
}
if let Some(Some(preferred_username)) = &preferred_username {
if !is_valid_preferred_username(preferred_username.trim()) {
if let Some(Some(display_name)) = &display_name {
if !is_valid_display_name(display_name.trim()) {
return Err(ApiError::err("invalid_username").into());
}
}
if let Some(Some(matrix_user_id)) = &matrix_user_id {
if !is_valid_matrix_id(matrix_user_id) {
return Err(ApiError::err("invalid_matrix_id").into());
}
}
let local_user_id = local_user_view.local_user.id;
let person_id = local_user_view.person.id;
let password_encrypted = match &data.new_password {
Some(new_password) => {
match &data.new_password_verify {
Some(new_password_verify) => {
password_length_check(&new_password)?;
// Make sure passwords match
if new_password != new_password_verify {
return Err(ApiError::err("passwords_dont_match").into());
}
// Check the old password
match &data.old_password {
Some(old_password) => {
let valid: bool =
verify(old_password, &local_user_view.local_user.password_encrypted)
.unwrap_or(false);
if !valid {
return Err(ApiError::err("password_incorrect").into());
}
let new_password = new_password.to_owned();
let user = blocking(context.pool(), move |conn| {
LocalUser::update_password(conn, local_user_id, &new_password)
})
.await??;
user.password_encrypted
}
None => return Err(ApiError::err("password_incorrect").into()),
}
}
None => return Err(ApiError::err("passwords_dont_match").into()),
}
}
None => local_user_view.local_user.password_encrypted,
};
let default_listing_type = data.default_listing_type;
let default_sort_type = data.default_sort_type;
let password_encrypted = local_user_view.local_user.password_encrypted;
let person_form = PersonForm {
name: local_user_view.person.name,
avatar,
banner,
inbox_url: None,
preferred_username,
display_name,
published: None,
updated: Some(naive_now()),
banned: None,
@ -267,6 +234,7 @@ impl Perform for SaveUserSettings {
email,
password_encrypted,
show_nsfw: data.show_nsfw,
show_scores: data.show_scores,
theme: data.theme.to_owned(),
default_sort_type,
default_listing_type,
@ -301,6 +269,49 @@ impl Perform for SaveUserSettings {
}
}
#[async_trait::async_trait(?Send)]
impl Perform for ChangePassword {
type Response = LoginResponse;
async fn perform(
&self,
context: &Data<LemmyContext>,
_websocket_id: Option<ConnectionId>,
) -> Result<LoginResponse, LemmyError> {
let data: &ChangePassword = &self;
let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?;
password_length_check(&data.new_password)?;
// Make sure passwords match
if data.new_password != data.new_password_verify {
return Err(ApiError::err("passwords_dont_match").into());
}
// Check the old password
let valid: bool = verify(
&data.old_password,
&local_user_view.local_user.password_encrypted,
)
.unwrap_or(false);
if !valid {
return Err(ApiError::err("password_incorrect").into());
}
let local_user_id = local_user_view.local_user.id;
let new_password = data.new_password.to_owned();
let updated_local_user = blocking(context.pool(), move |conn| {
LocalUser::update_password(conn, local_user_id, &new_password)
})
.await??;
// Return the jwt
Ok(LoginResponse {
jwt: Claims::jwt(updated_local_user.id.0)?,
})
}
}
#[async_trait::async_trait(?Send)]
impl Perform for AddAdmin {
type Response = AddAdminResponse;
@ -394,10 +405,9 @@ impl Perform for BanPerson {
.await??;
// Communities
blocking(context.pool(), move |conn: &'_ _| {
Community::update_removed_for_creator(conn, banned_person_id, true)
})
.await??;
// Remove all communities where they're the top mod
// TODO couldn't get group by's working in diesel,
// for now, remove the communities manually
// Comments
blocking(context.pool(), move |conn: &'_ _| {

View File

@ -418,3 +418,12 @@ pub fn password_length_check(pass: &str) -> Result<(), LemmyError> {
Ok(())
}
}
/// Checks the site description length
pub fn site_description_length_check(description: &str) -> Result<(), LemmyError> {
if description.len() > 150 {
Err(ApiError::err("site_description_length_overflow").into())
} else {
Ok(())
}
}

View File

@ -39,32 +39,41 @@ pub struct GetCaptchaResponse {
#[derive(Serialize)]
pub struct CaptchaResponse {
pub png: String, // A Base64 encoded png
pub wav: Option<String>, // A Base64 encoded wav audio
pub png: String, // A Base64 encoded png
pub wav: String, // A Base64 encoded wav audio
pub uuid: String,
}
#[derive(Deserialize)]
pub struct SaveUserSettings {
pub show_nsfw: Option<bool>,
pub show_scores: Option<bool>,
pub theme: Option<String>,
pub default_sort_type: Option<i16>,
pub default_listing_type: Option<i16>,
pub lang: Option<String>,
pub avatar: Option<String>,
pub banner: Option<String>,
pub preferred_username: Option<String>,
pub display_name: Option<String>,
pub email: Option<String>,
pub bio: Option<String>,
pub matrix_user_id: Option<String>,
pub show_avatars: Option<bool>,
pub new_password: Option<String>,
pub new_password_verify: Option<String>,
pub old_password: Option<String>,
pub show_avatars: Option<bool>,
pub send_notifications_to_email: Option<bool>,
pub auth: String,
}
#[derive(Deserialize)]
pub struct ChangePassword {
pub new_password: String,
pub new_password_verify: String,
pub old_password: String,
pub auth: String,
}
#[derive(Serialize)]
pub struct LoginResponse {
pub jwt: String,

View File

@ -18,7 +18,6 @@ use lemmy_db_views_moderator::{
mod_sticky_post_view::ModStickyPostView,
};
use serde::{Deserialize, Serialize};
use url::Url;
#[derive(Deserialize, Debug)]
pub struct Search {
@ -65,9 +64,10 @@ pub struct GetModlogResponse {
#[derive(Deserialize)]
pub struct CreateSite {
pub name: String,
pub sidebar: Option<String>,
pub description: Option<String>,
pub icon: Option<Url>,
pub banner: Option<Url>,
pub icon: Option<String>,
pub banner: Option<String>,
pub enable_downvotes: bool,
pub open_registration: bool,
pub enable_nsfw: bool,
@ -77,6 +77,7 @@ pub struct CreateSite {
#[derive(Deserialize)]
pub struct EditSite {
pub name: String,
pub sidebar: Option<String>,
pub description: Option<String>,
pub icon: Option<String>,
pub banner: Option<String>,

View File

@ -75,7 +75,6 @@ impl PerformCrud for CreateCommunity {
description: data.description.to_owned(),
icon,
banner,
creator_id: local_user_view.person.id,
nsfw: data.nsfw,
actor_id: Some(community_actor_id.to_owned()),
private_key: Some(keypair.private_key),

View File

@ -7,7 +7,10 @@ use lemmy_db_schema::source::{
community::*,
moderator::{ModRemoveCommunity, ModRemoveCommunityForm},
};
use lemmy_db_views_actor::community_view::CommunityView;
use lemmy_db_views_actor::{
community_moderator_view::CommunityModeratorView,
community_view::CommunityView,
};
use lemmy_utils::{utils::naive_from_unix, ApiError, ConnectionId, LemmyError};
use lemmy_websocket::{LemmyContext, UserOperationCrud};
@ -23,13 +26,15 @@ impl PerformCrud for DeleteCommunity {
let data: &DeleteCommunity = &self;
let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?;
// Verify its the creator (only a creator can delete the community)
// Fetch the community mods
let community_id = data.community_id;
let read_community = blocking(context.pool(), move |conn| {
Community::read(conn, community_id)
let community_mods = blocking(context.pool(), move |conn| {
CommunityModeratorView::for_community(conn, community_id)
})
.await??;
if read_community.creator_id != local_user_view.person.id {
// Make sure deleter is the top mod
if local_user_view.person.id != community_mods[0].moderator.id {
return Err(ApiError::err("no_community_edit_allowed").into());
}

View File

@ -61,7 +61,6 @@ impl PerformCrud for EditCommunity {
let community_form = CommunityForm {
name: read_community.name,
title: data.title.to_owned(),
creator_id: read_community.creator_id,
description: data.description.to_owned(),
icon,
banner,

View File

@ -1,7 +1,18 @@
use crate::PerformCrud;
use actix_web::web::Data;
use lemmy_api_common::{blocking, get_local_user_view_from_jwt, is_admin, site::*};
use lemmy_db_queries::{source::site::Site_, Crud};
use lemmy_api_common::{
blocking,
get_local_user_view_from_jwt,
is_admin,
site::*,
site_description_length_check,
};
use lemmy_db_queries::{
diesel_option_overwrite,
diesel_option_overwrite_to_url,
source::site::Site_,
Crud,
};
use lemmy_db_schema::source::site::{Site, *};
use lemmy_db_views::site_view::SiteView;
use lemmy_utils::{
@ -36,11 +47,21 @@ impl PerformCrud for CreateSite {
// Make sure user is an admin
is_admin(&local_user_view)?;
let sidebar = diesel_option_overwrite(&data.sidebar);
let description = diesel_option_overwrite(&data.description);
let icon = diesel_option_overwrite_to_url(&data.icon)?;
let banner = diesel_option_overwrite_to_url(&data.banner)?;
if let Some(Some(desc)) = &description {
site_description_length_check(desc)?;
}
let site_form = SiteForm {
name: data.name.to_owned(),
description: data.description.to_owned(),
icon: Some(data.icon.to_owned().map(|url| url.into())),
banner: Some(data.banner.to_owned().map(|url| url.into())),
sidebar,
description,
icon,
banner,
creator_id: local_user_view.person.id,
enable_downvotes: data.enable_downvotes,
open_registration: data.open_registration,

View File

@ -43,6 +43,7 @@ impl PerformCrud for GetSite {
let create_site = CreateSite {
name: setup.site_name.to_owned(),
sidebar: None,
description: None,
icon: None,
banner: None,

View File

@ -5,8 +5,14 @@ use lemmy_api_common::{
get_local_user_view_from_jwt,
is_admin,
site::{EditSite, SiteResponse},
site_description_length_check,
};
use lemmy_db_queries::{
diesel_option_overwrite,
diesel_option_overwrite_to_url,
source::site::Site_,
Crud,
};
use lemmy_db_queries::{diesel_option_overwrite_to_url, source::site::Site_, Crud};
use lemmy_db_schema::{
naive_now,
source::site::{Site, SiteForm},
@ -39,12 +45,19 @@ impl PerformCrud for EditSite {
let found_site = blocking(context.pool(), move |conn| Site::read_simple(conn)).await??;
let sidebar = diesel_option_overwrite(&data.sidebar);
let description = diesel_option_overwrite(&data.description);
let icon = diesel_option_overwrite_to_url(&data.icon)?;
let banner = diesel_option_overwrite_to_url(&data.banner)?;
if let Some(Some(desc)) = &description {
site_description_length_check(desc)?;
}
let site_form = SiteForm {
name: data.name.to_owned(),
description: data.description.to_owned(),
sidebar,
description,
icon,
banner,
creator_id: found_site.creator_id,

View File

@ -133,6 +133,7 @@ impl PerformCrud for Register {
default_listing_type: Some(ListingType::Subscribed as i16),
lang: Some("browser".into()),
show_avatars: Some(true),
show_scores: Some(true),
send_notifications_to_email: Some(false),
};
@ -177,7 +178,6 @@ impl PerformCrud for Register {
name: default_community_name.to_string(),
title: "The Default Community".to_string(),
description: Some("The Default Community".to_string()),
creator_id: inserted_person.id,
actor_id: Some(actor_id.to_owned()),
private_key: Some(main_community_keypair.private_key),
public_key: Some(main_community_keypair.public_key),

View File

@ -1,6 +1,6 @@
use crate::{
extensions::{context::lemmy_context, group_extension::GroupExtension},
fetcher::{community::fetch_community_mods, person::get_or_fetch_and_upsert_person},
fetcher::community::fetch_community_mods,
generate_moderators_url,
objects::{
check_object_domain,
@ -140,24 +140,7 @@ impl FromApubToForm<GroupExt> for CommunityForm {
request_counter: &mut i32,
_mod_action_allowed: bool,
) -> Result<Self, LemmyError> {
let moderator_uris = fetch_community_mods(context, group, request_counter).await?;
let creator = if let Some(creator_uri) = moderator_uris.first() {
get_or_fetch_and_upsert_person(creator_uri, context, request_counter)
} else {
// NOTE: code for compatibility with lemmy v0.9.9
let creator_uri = group
.inner
.attributed_to()
.map(|a| a.as_many())
.flatten()
.map(|a| a.first())
.flatten()
.map(|a| a.as_xsd_any_uri())
.flatten()
.context(location_info!())?;
get_or_fetch_and_upsert_person(creator_uri, context, request_counter)
}
.await?;
fetch_community_mods(context, group, request_counter).await?;
let name = group
.inner
@ -215,7 +198,6 @@ impl FromApubToForm<GroupExt> for CommunityForm {
name,
title,
description,
creator_id: creator.id,
removed: None,
published: group.inner.published().map(|u| u.to_owned().naive_local()),
updated: group.inner.updated().map(|u| u.to_owned().naive_local()),

View File

@ -64,7 +64,8 @@ impl ToApub for DbPerson {
set_content_and_source(&mut person, bio)?;
}
if let Some(i) = self.preferred_username.to_owned() {
// In apub, the "name" is a display name
if let Some(i) = self.display_name.to_owned() {
person.set_name(i);
}
@ -161,7 +162,7 @@ impl FromApubToForm<PersonExt> for PersonForm {
.preferred_username()
.context(location_info!())?
.to_string();
let preferred_username: Option<String> = person
let display_name: Option<String> = person
.name()
.map(|n| n.one())
.flatten()
@ -176,12 +177,12 @@ impl FromApubToForm<PersonExt> for PersonForm {
.map(|s| s.to_owned().into());
check_slurs(&name)?;
check_slurs_opt(&preferred_username)?;
check_slurs_opt(&display_name)?;
check_slurs_opt(&bio)?;
Ok(PersonForm {
name,
preferred_username: Some(preferred_username),
display_name: Some(display_name),
banned: None,
deleted: None,
avatar: avatar.map(|o| o.map(|i| i.into())),

View File

@ -58,7 +58,6 @@ mod tests {
let new_community = CommunityForm {
name: "TIL_comment_agg".into(),
creator_id: inserted_person.id,
title: "nada".to_owned(),
..CommunityForm::default()
};
@ -142,5 +141,9 @@ mod tests {
Person::delete(&conn, another_inserted_person.id).unwrap();
let person_num_deleted = Person::delete(&conn, inserted_person.id).unwrap();
assert_eq!(1, person_num_deleted);
// Delete the community
let community_num_deleted = Community::delete(&conn, inserted_community.id).unwrap();
assert_eq!(1, community_num_deleted);
}
}

View File

@ -62,7 +62,6 @@ mod tests {
let new_community = CommunityForm {
name: "TIL_community_agg".into(),
creator_id: inserted_person.id,
title: "nada".to_owned(),
..CommunityForm::default()
};
@ -71,7 +70,6 @@ mod tests {
let another_community = CommunityForm {
name: "TIL_community_agg_2".into(),
creator_id: inserted_person.id,
title: "nada".to_owned(),
..CommunityForm::default()
};
@ -169,6 +167,14 @@ mod tests {
let person_num_deleted = Person::delete(&conn, inserted_person.id).unwrap();
assert_eq!(1, person_num_deleted);
// Delete the community
let community_num_deleted = Community::delete(&conn, inserted_community.id).unwrap();
assert_eq!(1, community_num_deleted);
let another_community_num_deleted =
Community::delete(&conn, another_inserted_community.id).unwrap();
assert_eq!(1, another_community_num_deleted);
// Should be none found, since the creator was deleted
let after_delete = CommunityAggregates::read(&conn, inserted_community.id);
assert!(after_delete.is_err());

View File

@ -58,7 +58,6 @@ mod tests {
let new_community = CommunityForm {
name: "TIL_site_agg".into(),
creator_id: inserted_person.id,
title: "nada".to_owned(),
..CommunityForm::default()
};
@ -160,6 +159,10 @@ mod tests {
assert_eq!(1, person_num_deleted);
Person::delete(&conn, another_inserted_person.id).unwrap();
// Delete the community
let community_num_deleted = Community::delete(&conn, inserted_community.id).unwrap();
assert_eq!(1, community_num_deleted);
// Should be none found
let after_delete = PersonAggregates::read(&conn, inserted_person.id);
assert!(after_delete.is_err());

View File

@ -62,7 +62,6 @@ mod tests {
let new_community = CommunityForm {
name: "TIL_community_agg".into(),
creator_id: inserted_person.id,
title: "nada".to_owned(),
..CommunityForm::default()
};
@ -149,6 +148,10 @@ mod tests {
let person_num_deleted = Person::delete(&conn, inserted_person.id).unwrap();
assert_eq!(1, person_num_deleted);
// Delete the community
let community_num_deleted = Community::delete(&conn, inserted_community.id).unwrap();
assert_eq!(1, community_num_deleted);
// Should be none found, since the creator was deleted
let after_delete = PostAggregates::read(&conn, inserted_post.id);
assert!(after_delete.is_err());

View File

@ -49,6 +49,7 @@ mod tests {
let site_form = SiteForm {
name: "test_site".into(),
sidebar: None,
description: None,
icon: None,
banner: None,
@ -63,7 +64,6 @@ mod tests {
let new_community = CommunityForm {
name: "TIL_site_agg".into(),
creator_id: inserted_person.id,
title: "nada".to_owned(),
..CommunityForm::default()
};
@ -118,6 +118,10 @@ mod tests {
let person_num_deleted = Person::delete(&conn, inserted_person.id).unwrap();
assert_eq!(1, person_num_deleted);
// Delete the community
let community_num_deleted = Community::delete(&conn, inserted_community.id).unwrap();
assert_eq!(1, community_num_deleted);
let after_delete = SiteAggregates::read(&conn);
assert!(after_delete.is_err());
}

View File

@ -254,7 +254,6 @@ mod tests {
let new_community = CommunityForm {
name: "test community".to_string(),
title: "nada".to_owned(),
creator_id: inserted_person.id,
..CommunityForm::default()
};

View File

@ -26,7 +26,6 @@ mod safe_type {
name,
title,
description,
creator_id,
removed,
published,
updated,
@ -46,7 +45,6 @@ mod safe_type {
name,
title,
description,
creator_id,
removed,
published,
updated,
@ -122,16 +120,6 @@ pub trait Community_ {
community_id: CommunityId,
new_removed: bool,
) -> Result<Community, Error>;
fn update_removed_for_creator(
conn: &PgConnection,
for_creator_id: PersonId,
new_removed: bool,
) -> Result<Vec<Community>, Error>;
fn update_creator(
conn: &PgConnection,
community_id: CommunityId,
new_creator_id: PersonId,
) -> Result<Community, Error>;
fn distinct_federated_communities(conn: &PgConnection) -> Result<Vec<String>, Error>;
fn read_from_followers_url(
conn: &PgConnection,
@ -170,28 +158,6 @@ impl Community_ for Community {
.get_result::<Self>(conn)
}
fn update_removed_for_creator(
conn: &PgConnection,
for_creator_id: PersonId,
new_removed: bool,
) -> Result<Vec<Community>, Error> {
use lemmy_db_schema::schema::community::dsl::*;
diesel::update(community.filter(creator_id.eq(for_creator_id)))
.set((removed.eq(new_removed), updated.eq(naive_now())))
.get_results::<Self>(conn)
}
fn update_creator(
conn: &PgConnection,
community_id: CommunityId,
new_creator_id: PersonId,
) -> Result<Community, Error> {
use lemmy_db_schema::schema::community::dsl::*;
diesel::update(community.find(community_id))
.set((creator_id.eq(new_creator_id), updated.eq(naive_now())))
.get_result::<Self>(conn)
}
fn distinct_federated_communities(conn: &PgConnection) -> Result<Vec<String>, Error> {
use lemmy_db_schema::schema::community::dsl::*;
community.select(actor_id).distinct().load::<String>(conn)
@ -363,7 +329,6 @@ mod tests {
let new_community = CommunityForm {
name: "TIL".into(),
creator_id: inserted_person.id,
title: "nada".to_owned(),
..CommunityForm::default()
};
@ -372,7 +337,6 @@ mod tests {
let expected_community = Community {
id: inserted_community.id,
creator_id: inserted_person.id,
name: "TIL".into(),
title: "nada".to_owned(),
description: None,

View File

@ -24,6 +24,7 @@ mod safe_settings_type {
show_avatars,
send_notifications_to_email,
validator_time,
show_scores,
);
impl ToSafeSettings for LocalUser {
@ -43,6 +44,7 @@ mod safe_settings_type {
show_avatars,
send_notifications_to_email,
validator_time,
show_scores,
)
}
}

View File

@ -224,7 +224,6 @@ mod tests {
let new_community = CommunityForm {
name: "mod_community".to_string(),
title: "nada".to_owned(),
creator_id: inserted_person.id,
..CommunityForm::default()
};

View File

@ -15,7 +15,7 @@ mod safe_type {
type Columns = (
id,
name,
preferred_username,
display_name,
avatar,
banned,
published,
@ -37,7 +37,7 @@ mod safe_type {
(
id,
name,
preferred_username,
display_name,
avatar,
banned,
published,
@ -63,7 +63,7 @@ mod safe_type_alias_1 {
type Columns = (
id,
name,
preferred_username,
display_name,
avatar,
banned,
published,
@ -85,7 +85,7 @@ mod safe_type_alias_1 {
(
id,
name,
preferred_username,
display_name,
avatar,
banned,
published,
@ -111,7 +111,7 @@ mod safe_type_alias_2 {
type Columns = (
id,
name,
preferred_username,
display_name,
avatar,
banned,
published,
@ -133,7 +133,7 @@ mod safe_type_alias_2 {
(
id,
name,
preferred_username,
display_name,
avatar,
banned,
published,
@ -236,7 +236,7 @@ impl Person_ for Person {
diesel::update(person.find(person_id))
.set((
preferred_username.eq::<Option<String>>(None),
display_name.eq::<Option<String>>(None),
bio.eq::<Option<String>>(None),
matrix_user_id.eq::<Option<String>>(None),
deleted.eq(true),
@ -264,7 +264,7 @@ mod tests {
let expected_person = Person {
id: inserted_person.id,
name: "holly".into(),
preferred_username: None,
display_name: None,
avatar: None,
banner: None,
banned: false,

View File

@ -105,7 +105,6 @@ mod tests {
let new_community = CommunityForm {
name: "test community lake".to_string(),
title: "nada".to_owned(),
creator_id: inserted_person.id,
..CommunityForm::default()
};

View File

@ -281,7 +281,6 @@ mod tests {
let new_community = CommunityForm {
name: "test community_3".to_string(),
title: "nada".to_owned(),
creator_id: inserted_person.id,
..CommunityForm::default()
};

View File

@ -78,7 +78,6 @@ table! {
name -> Varchar,
title -> Varchar,
description -> Nullable<Text>,
creator_id -> Int4,
removed -> Bool,
published -> Timestamp,
updated -> Nullable<Timestamp>,
@ -154,6 +153,7 @@ table! {
show_avatars -> Bool,
send_notifications_to_email -> Bool,
validator_time -> Timestamp,
show_scores -> Bool,
}
}
@ -270,7 +270,7 @@ table! {
person (id) {
id -> Int4,
name -> Varchar,
preferred_username -> Nullable<Varchar>,
display_name -> Nullable<Varchar>,
avatar -> Nullable<Varchar>,
banned -> Bool,
published -> Timestamp,
@ -421,7 +421,7 @@ table! {
site (id) {
id -> Int4,
name -> Varchar,
description -> Nullable<Text>,
sidebar -> Nullable<Text>,
creator_id -> Int4,
published -> Timestamp,
updated -> Nullable<Timestamp>,
@ -430,6 +430,7 @@ table! {
enable_nsfw -> Bool,
icon -> Nullable<Varchar>,
banner -> Nullable<Varchar>,
description -> Nullable<Text>,
}
}
@ -470,7 +471,7 @@ table! {
person_alias_1 (id) {
id -> Int4,
name -> Varchar,
preferred_username -> Nullable<Varchar>,
display_name -> Nullable<Varchar>,
avatar -> Nullable<Varchar>,
banned -> Bool,
published -> Timestamp,
@ -494,7 +495,7 @@ table! {
person_alias_2 (id) {
id -> Int4,
name -> Varchar,
preferred_username -> Nullable<Varchar>,
display_name -> Nullable<Varchar>,
avatar -> Nullable<Varchar>,
banned -> Bool,
published -> Timestamp,
@ -532,7 +533,6 @@ joinable!(comment_like -> post (post_id));
joinable!(comment_report -> comment (comment_id));
joinable!(comment_saved -> comment (comment_id));
joinable!(comment_saved -> person (person_id));
joinable!(community -> person (creator_id));
joinable!(community_aggregates -> community (community_id));
joinable!(community_follower -> community (community_id));
joinable!(community_follower -> person (person_id));

View File

@ -56,8 +56,8 @@ pub struct CommentAlias1 {
pub struct CommentForm {
pub creator_id: PersonId,
pub post_id: PostId,
pub parent_id: Option<CommentId>,
pub content: String,
pub parent_id: Option<CommentId>,
pub removed: Option<bool>,
pub read: Option<bool>,
pub published: Option<chrono::NaiveDateTime>,

View File

@ -13,7 +13,6 @@ pub struct Community {
pub name: String,
pub title: String,
pub description: Option<String>,
pub creator_id: PersonId,
pub removed: bool,
pub published: chrono::NaiveDateTime,
pub updated: Option<chrono::NaiveDateTime>,
@ -39,7 +38,6 @@ pub struct CommunitySafe {
pub name: String,
pub title: String,
pub description: Option<String>,
pub creator_id: PersonId,
pub removed: bool,
pub published: chrono::NaiveDateTime,
pub updated: Option<chrono::NaiveDateTime>,
@ -57,7 +55,6 @@ pub struct CommunityForm {
pub name: String,
pub title: String,
pub description: Option<String>,
pub creator_id: PersonId,
pub removed: Option<bool>,
pub published: Option<chrono::NaiveDateTime>,
pub updated: Option<chrono::NaiveDateTime>,

View File

@ -16,6 +16,7 @@ pub struct LocalUser {
pub show_avatars: bool,
pub send_notifications_to_email: bool,
pub validator_time: chrono::NaiveDateTime,
pub show_scores: bool,
}
// TODO redo these, check table defaults
@ -32,6 +33,7 @@ pub struct LocalUserForm {
pub lang: Option<String>,
pub show_avatars: Option<bool>,
pub send_notifications_to_email: Option<bool>,
pub show_scores: Option<bool>,
}
/// A local user view that removes password encrypted
@ -49,4 +51,5 @@ pub struct LocalUserSettings {
pub show_avatars: bool,
pub send_notifications_to_email: bool,
pub validator_time: chrono::NaiveDateTime,
pub show_scores: bool,
}

View File

@ -10,7 +10,7 @@ use serde::Serialize;
pub struct Person {
pub id: PersonId,
pub name: String,
pub preferred_username: Option<String>,
pub display_name: Option<String>,
pub avatar: Option<DbUrl>,
pub banned: bool,
pub published: chrono::NaiveDateTime,
@ -35,7 +35,7 @@ pub struct Person {
pub struct PersonSafe {
pub id: PersonId,
pub name: String,
pub preferred_username: Option<String>,
pub display_name: Option<String>,
pub avatar: Option<DbUrl>,
pub banned: bool,
pub published: chrono::NaiveDateTime,
@ -56,7 +56,7 @@ pub struct PersonSafe {
pub struct PersonAlias1 {
pub id: PersonId,
pub name: String,
pub preferred_username: Option<String>,
pub display_name: Option<String>,
pub avatar: Option<DbUrl>,
pub banned: bool,
pub published: chrono::NaiveDateTime,
@ -80,7 +80,7 @@ pub struct PersonAlias1 {
pub struct PersonSafeAlias1 {
pub id: PersonId,
pub name: String,
pub preferred_username: Option<String>,
pub display_name: Option<String>,
pub avatar: Option<DbUrl>,
pub banned: bool,
pub published: chrono::NaiveDateTime,
@ -101,7 +101,7 @@ pub struct PersonSafeAlias1 {
pub struct PersonAlias2 {
pub id: PersonId,
pub name: String,
pub preferred_username: Option<String>,
pub display_name: Option<String>,
pub avatar: Option<DbUrl>,
pub banned: bool,
pub published: chrono::NaiveDateTime,
@ -125,7 +125,7 @@ pub struct PersonAlias2 {
pub struct PersonSafeAlias2 {
pub id: PersonId,
pub name: String,
pub preferred_username: Option<String>,
pub display_name: Option<String>,
pub avatar: Option<DbUrl>,
pub banned: bool,
pub published: chrono::NaiveDateTime,
@ -145,7 +145,7 @@ pub struct PersonSafeAlias2 {
#[table_name = "person"]
pub struct PersonForm {
pub name: String,
pub preferred_username: Option<Option<String>>,
pub display_name: Option<Option<String>>,
pub avatar: Option<Option<DbUrl>>,
pub banned: Option<bool>,
pub published: Option<chrono::NaiveDateTime>,

View File

@ -35,16 +35,16 @@ pub struct Post {
#[table_name = "post"]
pub struct PostForm {
pub name: String,
pub url: Option<DbUrl>,
pub body: Option<String>,
pub creator_id: PersonId,
pub community_id: CommunityId,
pub nsfw: bool,
pub url: Option<DbUrl>,
pub body: Option<String>,
pub removed: Option<bool>,
pub locked: Option<bool>,
pub published: Option<chrono::NaiveDateTime>,
pub updated: Option<chrono::NaiveDateTime>,
pub deleted: Option<bool>,
pub nsfw: bool,
pub stickied: Option<bool>,
pub embed_title: Option<String>,
pub embed_description: Option<String>,

View File

@ -6,7 +6,7 @@ use serde::Serialize;
pub struct Site {
pub id: i32,
pub name: String,
pub description: Option<String>,
pub sidebar: Option<String>,
pub creator_id: PersonId,
pub published: chrono::NaiveDateTime,
pub updated: Option<chrono::NaiveDateTime>,
@ -15,13 +15,14 @@ pub struct Site {
pub enable_nsfw: bool,
pub icon: Option<DbUrl>,
pub banner: Option<DbUrl>,
pub description: Option<String>,
}
#[derive(Insertable, AsChangeset)]
#[table_name = "site"]
pub struct SiteForm {
pub name: String,
pub description: Option<String>,
pub sidebar: Option<Option<String>>,
pub creator_id: PersonId,
pub updated: Option<chrono::NaiveDateTime>,
pub enable_downvotes: bool,
@ -30,4 +31,5 @@ pub struct SiteForm {
// when you want to null out a column, you have to send Some(None)), since sending None means you just don't want to update that column.
pub icon: Option<Option<DbUrl>>,
pub banner: Option<Option<DbUrl>>,
pub description: Option<Option<String>>,
}

View File

@ -462,7 +462,6 @@ mod tests {
let new_community = CommunityForm {
name: "test community 5".to_string(),
title: "nada".to_owned(),
creator_id: inserted_person.id,
..CommunityForm::default()
};
@ -519,7 +518,7 @@ mod tests {
creator: PersonSafe {
id: inserted_person.id,
name: "timmy".into(),
preferred_username: None,
display_name: None,
published: inserted_person.published,
avatar: None,
actor_id: inserted_person.actor_id.to_owned(),
@ -567,7 +566,6 @@ mod tests {
local: true,
title: "nada".to_owned(),
description: None,
creator_id: inserted_person.id,
updated: None,
banner: None,
published: inserted_community.published,

View File

@ -263,7 +263,7 @@ impl<'a> PostQueryBuilder<'a> {
community_person_ban::table.on(
post::community_id
.eq(community_person_ban::community_id)
.and(community_person_ban::person_id.eq(community::creator_id)),
.and(community_person_ban::person_id.eq(post::creator_id)),
),
)
.inner_join(post_aggregates::table)
@ -462,7 +462,6 @@ mod tests {
let new_community = CommunityForm {
name: community_name.to_owned(),
title: "nada".to_owned(),
creator_id: inserted_person.id,
..CommunityForm::default()
};
@ -541,7 +540,7 @@ mod tests {
creator: PersonSafe {
id: inserted_person.id,
name: person_name,
preferred_username: None,
display_name: None,
published: inserted_person.published,
avatar: None,
actor_id: inserted_person.actor_id.to_owned(),
@ -568,7 +567,6 @@ mod tests {
local: true,
title: "nada".to_owned(),
description: None,
creator_id: inserted_person.id,
updated: None,
banner: None,
published: inserted_community.published,

View File

@ -12,11 +12,8 @@ use lemmy_db_queries::{
ViewToVec,
};
use lemmy_db_schema::{
schema::{community, community_aggregates, community_follower, person},
source::{
community::{Community, CommunityFollower, CommunitySafe},
person::{Person, PersonSafe},
},
schema::{community, community_aggregates, community_follower},
source::community::{Community, CommunityFollower, CommunitySafe},
CommunityId,
PersonId,
};
@ -25,14 +22,12 @@ use serde::Serialize;
#[derive(Debug, Serialize, Clone)]
pub struct CommunityView {
pub community: CommunitySafe,
pub creator: PersonSafe,
pub subscribed: bool,
pub counts: CommunityAggregates,
}
type CommunityViewTuple = (
CommunitySafe,
PersonSafe,
CommunityAggregates,
Option<CommunityFollower>,
);
@ -46,9 +41,8 @@ impl CommunityView {
// The left join below will return None in this case
let person_id_join = my_person_id.unwrap_or(PersonId(-1));
let (community, creator, counts, follower) = community::table
let (community, counts, follower) = community::table
.find(community_id)
.inner_join(person::table)
.inner_join(community_aggregates::table)
.left_join(
community_follower::table.on(
@ -59,7 +53,6 @@ impl CommunityView {
)
.select((
Community::safe_columns_tuple(),
Person::safe_columns_tuple(),
community_aggregates::all_columns,
community_follower::all_columns.nullable(),
))
@ -67,7 +60,6 @@ impl CommunityView {
Ok(CommunityView {
community,
creator,
subscribed: follower.is_some(),
counts,
})
@ -165,7 +157,6 @@ impl<'a> CommunityQueryBuilder<'a> {
let person_id_join = self.my_person_id.unwrap_or(PersonId(-1));
let mut query = community::table
.inner_join(person::table)
.inner_join(community_aggregates::table)
.left_join(
community_follower::table.on(
@ -176,7 +167,6 @@ impl<'a> CommunityQueryBuilder<'a> {
)
.select((
Community::safe_columns_tuple(),
Person::safe_columns_tuple(),
community_aggregates::all_columns,
community_follower::all_columns.nullable(),
))
@ -193,6 +183,7 @@ impl<'a> CommunityQueryBuilder<'a> {
match self.sort {
SortType::New => query = query.order_by(community::published.desc()),
SortType::TopAll => query = query.order_by(community_aggregates::subscribers.desc()),
SortType::TopMonth => query = query.order_by(community_aggregates::users_active_month.desc()),
// Covers all other sorts, including hot
_ => {
query = query
@ -236,9 +227,8 @@ impl ViewToVec for CommunityView {
.iter()
.map(|a| Self {
community: a.0.to_owned(),
creator: a.1.to_owned(),
counts: a.2.to_owned(),
subscribed: a.3.is_some(),
counts: a.1.to_owned(),
subscribed: a.2.is_some(),
})
.collect::<Vec<Self>>()
}

View File

@ -24,12 +24,12 @@ impl Default for Settings {
impl Default for DatabaseConfig {
fn default() -> Self {
Self {
user: "lemmy".into(),
user: Some("lemmy".to_string()),
password: "password".into(),
host: "localhost".into(),
port: 5432,
database: "lemmy".into(),
pool_size: 5,
port: Some(5432),
database: Some("lemmy".to_string()),
pool_size: Some(5),
}
}
}

View File

@ -16,7 +16,7 @@ use deser_hjson::from_str;
use merge::Merge;
use std::{env, fs, io::Error, net::IpAddr, sync::RwLock};
pub(crate) mod defaults;
pub mod defaults;
pub mod structs;
static CONFIG_FILE: &str = "config/config.hjson";
@ -60,7 +60,11 @@ impl Settings {
let conf = self.database();
format!(
"postgres://{}:{}@{}:{}/{}",
conf.user, conf.password, conf.host, conf.port, conf.database,
conf.user(),
conf.password,
conf.host,
conf.port(),
conf.database(),
)
}

View File

@ -27,12 +27,40 @@ pub struct CaptchaConfig {
#[derive(Debug, Deserialize, Clone)]
pub struct DatabaseConfig {
pub user: String,
pub(super) user: Option<String>,
pub password: String,
pub host: String,
pub port: i32,
pub database: String,
pub pool_size: u32,
pub(super) port: Option<i32>,
pub(super) database: Option<String>,
pub(super) pool_size: Option<u32>,
}
impl DatabaseConfig {
pub fn user(&self) -> String {
self
.user
.to_owned()
.unwrap_or_else(|| DatabaseConfig::default().user.expect("missing user"))
}
pub fn port(&self) -> i32 {
self
.port
.unwrap_or_else(|| DatabaseConfig::default().port.expect("missing port"))
}
pub fn database(&self) -> String {
self.database.to_owned().unwrap_or_else(|| {
DatabaseConfig::default()
.database
.expect("missing database")
})
}
pub fn pool_size(&self) -> u32 {
self.pool_size.unwrap_or_else(|| {
DatabaseConfig::default()
.pool_size
.expect("missing pool_size")
})
}
}
#[derive(Debug, Deserialize, Clone)]

View File

@ -1,7 +1,8 @@
use crate::utils::{
is_valid_community_name,
is_valid_display_name,
is_valid_matrix_id,
is_valid_post_title,
is_valid_preferred_username,
is_valid_username,
remove_slurs,
scrape_text_for_mentions,
@ -29,9 +30,15 @@ fn test_valid_register_username() {
}
#[test]
fn test_valid_preferred_username() {
assert!(is_valid_preferred_username("hello @there"));
assert!(!is_valid_preferred_username("@hello there"));
fn test_valid_display_name() {
assert!(is_valid_display_name("hello @there"));
assert!(!is_valid_display_name("@hello there"));
// Make sure zero-space with an @ doesn't work
assert!(!is_valid_display_name(&format!(
"{}@my name is",
'\u{200b}'
)));
}
#[test]
@ -50,6 +57,14 @@ fn test_valid_post_title() {
assert!(!is_valid_post_title("\n \n \n \n ")); // tabs/spaces/newlines
}
#[test]
fn test_valid_matrix_id() {
assert!(is_valid_matrix_id("@dess:matrix.org"));
assert!(!is_valid_matrix_id("dess:matrix.org"));
assert!(!is_valid_matrix_id(" @dess:matrix.org"));
assert!(!is_valid_matrix_id("@dess:matrix.org t"));
}
#[test]
fn test_slur_filter() {
let test =

View File

@ -15,6 +15,7 @@ lazy_static! {
static ref VALID_USERNAME_REGEX: Regex = Regex::new(r"^[a-zA-Z0-9_]{3,20}$").expect("compile regex");
static ref VALID_COMMUNITY_NAME_REGEX: Regex = Regex::new(r"^[a-z0-9_]{3,20}$").expect("compile regex");
static ref VALID_POST_TITLE_REGEX: Regex = Regex::new(r".*\S.*").expect("compile regex");
static ref VALID_MATRIX_ID_REGEX: Regex = Regex::new(r"^@[A-Za-z0-9._=-]+:[A-Za-z0-9.-]+\.[A-Za-z]{2,}$").expect("compile regex");
}
pub fn naive_from_unix(time: i64) -> NaiveDateTime {
@ -108,10 +109,15 @@ pub fn is_valid_username(name: &str) -> bool {
}
// Can't do a regex here, reverse lookarounds not supported
pub fn is_valid_preferred_username(preferred_username: &str) -> bool {
!preferred_username.starts_with('@')
&& preferred_username.chars().count() >= 3
&& preferred_username.chars().count() <= 20
pub fn is_valid_display_name(name: &str) -> bool {
!name.starts_with('@')
&& !name.starts_with('\u{200b}')
&& name.chars().count() >= 3
&& name.chars().count() <= 20
}
pub fn is_valid_matrix_id(matrix_id: &str) -> bool {
VALID_MATRIX_ID_REGEX.is_match(matrix_id)
}
pub fn is_valid_community_name(name: &str) -> bool {

View File

@ -1 +1 @@
pub const VERSION: &str = "0.10.2";
pub const VERSION: &str = "0.10.3";

View File

@ -123,6 +123,7 @@ pub enum UserOperation {
PostJoin,
CommunityJoin,
ModJoin,
ChangePassword,
}
#[derive(EnumString, ToString, Debug, Clone)]

View File

@ -49,9 +49,6 @@ FROM alpine:3.12 as lemmy
# Install libpq for postgres
RUN apk add libpq
# Install Espeak for captchas
RUN apk add espeak
RUN addgroup -g 1000 lemmy
RUN adduser -D -s /bin/sh -u 1000 -G lemmy lemmy

View File

@ -9,4 +9,4 @@ mkdir -p volumes/pictrs
sudo chown -R 991:991 volumes/pictrs
sudo docker build ../../ --file ../dev/volume_mount.dockerfile -t lemmy-dev:latest
sudo docker-compose pull --ignore-pull-failures || true
sudo docker-compose up -d
sudo docker-compose up

View File

@ -19,9 +19,9 @@ RUN --mount=type=cache,target=/app/target \
FROM ubuntu:20.10
# Install libpq for postgres and espeak
# Install libpq for postgres
RUN apt-get update -y
RUN apt-get install -y libpq-dev espeak
RUN apt-get install -y libpq-dev
# Copy resources
COPY --from=rust /app/lemmy_server /app/lemmy

View File

@ -22,9 +22,9 @@ RUN cp ./target/release/lemmy_server /app/lemmy_server
# The Debian runner
FROM debian:buster-slim as lemmy
# Install libpq for postgres and espeak for captchas
# Install libpq for postgres
RUN apt-get update \
&& apt-get -y install --no-install-recommends espeak postgresql-client libc6 libssl1.1 \
&& apt-get -y install --no-install-recommends postgresql-client libc6 libssl1.1 \
&& rm -rf /var/lib/apt/lists/*
RUN addgroup --gid 1000 lemmy

View File

@ -12,7 +12,7 @@ services:
restart: always
lemmy:
image: dessalines/lemmy:0.10.2
image: dessalines/lemmy:0.10.3
ports:
- "127.0.0.1:8536:8536"
restart: always
@ -26,7 +26,7 @@ services:
- iframely
lemmy-ui:
image: dessalines/lemmy-ui:0.10.2
image: dessalines/lemmy-ui:0.10.3
ports:
- "127.0.0.1:1235:1234"
restart: always

View File

@ -0,0 +1 @@
alter table local_user drop column show_scores;

View File

@ -0,0 +1 @@
alter table local_user add column show_scores boolean default true not null;

View File

@ -0,0 +1,2 @@
alter table site drop column description;
alter table site rename column sidebar to description;

View File

@ -0,0 +1,5 @@
-- Renaming description to sidebar
alter table site rename column description to sidebar;
-- Adding a short description column
alter table site add column description varchar(150);

View File

@ -0,0 +1,6 @@
alter table person rename display_name to preferred_username;
-- Regenerate the person_alias views
drop view person_alias_1, person_alias_2;
create view person_alias_1 as select * from person;
create view person_alias_2 as select * from person;

View File

@ -0,0 +1,6 @@
alter table person rename preferred_username to display_name;
-- Regenerate the person_alias views
drop view person_alias_1, person_alias_2;
create view person_alias_1 as select * from person;
create view person_alias_2 as select * from person;

View File

@ -0,0 +1 @@
drop index idx_community_aggregates_users_active_month;

View File

@ -0,0 +1,2 @@
create index idx_community_aggregates_users_active_month on community_aggregates (users_active_month desc);

View File

@ -0,0 +1,24 @@
-- Add the column back
alter table community add column creator_id int references person on update cascade on delete cascade;
-- Recreate the index
create index idx_community_creator on community (creator_id);
-- Add the data, selecting the highest mod
update community
set creator_id = sub.person_id
from (
select
cm.community_id,
cm.person_id
from
community_moderator cm
limit 1
) as sub
where id = sub.community_id;
-- Set to not null
alter table community alter column creator_id set not null;

View File

@ -0,0 +1,2 @@
-- Drop the column
alter table community drop column creator_id;

View File

@ -182,6 +182,10 @@ pub fn config(cfg: &mut web::ServiceConfig, rate_limit: &RateLimit) {
"/save_user_settings",
web::put().to(route_post::<SaveUserSettings>),
)
.route(
"/change_password",
web::put().to(route_post::<ChangePassword>),
)
.route("/report_count", web::get().to(route_get::<GetReportCount>)),
)
// Admin Actions

View File

@ -89,7 +89,6 @@ fn community_updates_2020_04_02(conn: &PgConnection) -> Result<(), LemmyError> {
name: ccommunity.name.to_owned(),
title: ccommunity.title.to_owned(),
description: ccommunity.description.to_owned(),
creator_id: ccommunity.creator_id,
removed: None,
deleted: None,
nsfw: None,

View File

@ -38,7 +38,7 @@ async fn main() -> Result<(), LemmyError> {
};
let manager = ConnectionManager::<PgConnection>::new(&db_url);
let pool = Pool::builder()
.max_size(settings.database().pool_size)
.max_size(settings.database().pool_size())
.build(manager)
.unwrap_or_else(|_| panic!("Error connecting to {}", db_url));