mirror of https://github.com/LemmyNet/lemmy.git
Adding views and functionality to registration application. #209
parent
9889d158f1
commit
e28977c987
|
@ -97,6 +97,9 @@
|
|||
open_registration: true
|
||||
enable_nsfw: true
|
||||
community_creation_admin_only: true
|
||||
require_email_verification: true
|
||||
require_application: true
|
||||
application_question: "string"
|
||||
}
|
||||
# the domain name of your instance (mandatory)
|
||||
hostname: "unset"
|
||||
|
|
|
@ -38,6 +38,15 @@ pub async fn match_websocket_operation(
|
|||
UserOperation::GetCaptcha => do_websocket_operation::<GetCaptcha>(context, id, op, data).await,
|
||||
UserOperation::GetReplies => do_websocket_operation::<GetReplies>(context, id, op, data).await,
|
||||
UserOperation::AddAdmin => do_websocket_operation::<AddAdmin>(context, id, op, data).await,
|
||||
UserOperation::GetUnreadRegistrationApplicationCount => {
|
||||
do_websocket_operation::<GetUnreadRegistrationApplicationCount>(context, id, op, data).await
|
||||
}
|
||||
UserOperation::ListRegistrationApplications => {
|
||||
do_websocket_operation::<ListRegistrationApplications>(context, id, op, data).await
|
||||
}
|
||||
UserOperation::ApproveRegistrationApplication => {
|
||||
do_websocket_operation::<ApproveRegistrationApplication>(context, id, op, data).await
|
||||
}
|
||||
UserOperation::BanPerson => do_websocket_operation::<BanPerson>(context, id, op, data).await,
|
||||
UserOperation::BlockPerson => {
|
||||
do_websocket_operation::<BlockPerson>(context, id, op, data).await
|
||||
|
|
|
@ -261,6 +261,7 @@ impl Perform for SaveUserSettings {
|
|||
show_new_post_notifs: data.show_new_post_notifs,
|
||||
send_notifications_to_email: data.send_notifications_to_email,
|
||||
email_verified: None,
|
||||
accepted_application: None,
|
||||
};
|
||||
|
||||
let local_user_res = blocking(context.pool(), move |conn| {
|
||||
|
|
|
@ -19,9 +19,15 @@ use lemmy_apub::{
|
|||
EndpointType,
|
||||
};
|
||||
use lemmy_db_schema::{
|
||||
diesel_option_overwrite,
|
||||
from_opt_str_to_opt_enum,
|
||||
newtypes::PersonId,
|
||||
source::{moderator::*, site::Site},
|
||||
source::{
|
||||
local_user::{LocalUser, LocalUserForm},
|
||||
moderator::*,
|
||||
registration_application::{RegistrationApplication, RegistrationApplicationForm},
|
||||
site::Site,
|
||||
},
|
||||
traits::{Crud, DeleteableOrRemoveable},
|
||||
DbPool,
|
||||
ListingType,
|
||||
|
@ -31,6 +37,10 @@ use lemmy_db_schema::{
|
|||
use lemmy_db_views::{
|
||||
comment_view::{CommentQueryBuilder, CommentView},
|
||||
post_view::{PostQueryBuilder, PostView},
|
||||
registration_application_view::{
|
||||
RegistrationApplicationQueryBuilder,
|
||||
RegistrationApplicationView,
|
||||
},
|
||||
site_view::SiteView,
|
||||
};
|
||||
use lemmy_db_views_actor::{
|
||||
|
@ -550,3 +560,131 @@ impl Perform for SaveSiteConfig {
|
|||
Ok(GetSiteConfigResponse { config_hjson })
|
||||
}
|
||||
}
|
||||
|
||||
/// Lists registration applications, filterable by undenied only.
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl Perform for ListRegistrationApplications {
|
||||
type Response = ListRegistrationApplicationsResponse;
|
||||
|
||||
async fn perform(
|
||||
&self,
|
||||
context: &Data<LemmyContext>,
|
||||
_websocket_id: Option<ConnectionId>,
|
||||
) -> Result<Self::Response, LemmyError> {
|
||||
let data = self;
|
||||
let local_user_view =
|
||||
get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
|
||||
|
||||
// Make sure user is an admin
|
||||
is_admin(&local_user_view)?;
|
||||
|
||||
let unread_only = data.unread_only;
|
||||
let verified_email_only = blocking(context.pool(), Site::read_simple)
|
||||
.await??
|
||||
.require_email_verification;
|
||||
|
||||
let page = data.page;
|
||||
let limit = data.limit;
|
||||
let registration_applications = blocking(context.pool(), move |conn| {
|
||||
RegistrationApplicationQueryBuilder::create(conn)
|
||||
.unread_only(unread_only)
|
||||
.verified_email_only(verified_email_only)
|
||||
.page(page)
|
||||
.limit(limit)
|
||||
.list()
|
||||
})
|
||||
.await??;
|
||||
|
||||
let res = Self::Response {
|
||||
registration_applications,
|
||||
};
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl Perform for ApproveRegistrationApplication {
|
||||
type Response = RegistrationApplicationResponse;
|
||||
|
||||
async fn perform(
|
||||
&self,
|
||||
context: &Data<LemmyContext>,
|
||||
_websocket_id: Option<ConnectionId>,
|
||||
) -> Result<Self::Response, LemmyError> {
|
||||
let data = self;
|
||||
let local_user_view =
|
||||
get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
|
||||
|
||||
let app_id = data.id;
|
||||
|
||||
// Only let admins do this
|
||||
is_admin(&local_user_view)?;
|
||||
|
||||
// Update the registration with reason, admin_id
|
||||
let deny_reason = diesel_option_overwrite(&data.deny_reason);
|
||||
let app_form = RegistrationApplicationForm {
|
||||
admin_id: Some(local_user_view.person.id),
|
||||
deny_reason,
|
||||
..RegistrationApplicationForm::default()
|
||||
};
|
||||
|
||||
let registration_application = blocking(context.pool(), move |conn| {
|
||||
RegistrationApplication::update(conn, app_id, &app_form)
|
||||
})
|
||||
.await??;
|
||||
|
||||
// Update the local_user row
|
||||
let local_user_form = LocalUserForm {
|
||||
accepted_application: Some(data.approve),
|
||||
..LocalUserForm::default()
|
||||
};
|
||||
|
||||
let approved_user_id = registration_application.local_user_id;
|
||||
blocking(context.pool(), move |conn| {
|
||||
LocalUser::update(conn, approved_user_id, &local_user_form)
|
||||
})
|
||||
.await??;
|
||||
|
||||
// Read the view
|
||||
let registration_application = blocking(context.pool(), move |conn| {
|
||||
RegistrationApplicationView::read(conn, app_id)
|
||||
})
|
||||
.await??;
|
||||
|
||||
Ok(Self::Response {
|
||||
registration_application,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl Perform for GetUnreadRegistrationApplicationCount {
|
||||
type Response = GetUnreadRegistrationApplicationCountResponse;
|
||||
|
||||
async fn perform(
|
||||
&self,
|
||||
context: &Data<LemmyContext>,
|
||||
_websocket_id: Option<ConnectionId>,
|
||||
) -> Result<Self::Response, LemmyError> {
|
||||
let data = self;
|
||||
let local_user_view =
|
||||
get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
|
||||
|
||||
// Only let admins do this
|
||||
is_admin(&local_user_view)?;
|
||||
|
||||
let verified_email_only = blocking(context.pool(), Site::read_simple)
|
||||
.await??
|
||||
.require_email_verification;
|
||||
|
||||
let registration_applications = blocking(context.pool(), move |conn| {
|
||||
RegistrationApplicationView::get_unread_count(conn, verified_email_only)
|
||||
})
|
||||
.await??;
|
||||
|
||||
Ok(Self::Response {
|
||||
registration_applications,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,6 +28,8 @@ pub struct Register {
|
|||
pub captcha_uuid: Option<String>,
|
||||
pub captcha_answer: Option<String>,
|
||||
pub honeypot: Option<String>,
|
||||
/// An answer is mandatory if require application is enabled on the server
|
||||
pub answer: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
|
|
|
@ -3,6 +3,7 @@ use lemmy_db_views::{
|
|||
comment_view::CommentView,
|
||||
local_user_view::LocalUserSettingsView,
|
||||
post_view::PostView,
|
||||
registration_application_view::RegistrationApplicationView,
|
||||
site_view::SiteView,
|
||||
};
|
||||
use lemmy_db_views_actor::{
|
||||
|
@ -97,6 +98,9 @@ pub struct CreateSite {
|
|||
pub open_registration: Option<bool>,
|
||||
pub enable_nsfw: Option<bool>,
|
||||
pub community_creation_admin_only: Option<bool>,
|
||||
pub require_email_verification: Option<bool>,
|
||||
pub require_application: Option<bool>,
|
||||
pub application_question: Option<String>,
|
||||
pub auth: String,
|
||||
}
|
||||
|
||||
|
@ -112,6 +116,8 @@ pub struct EditSite {
|
|||
pub enable_nsfw: Option<bool>,
|
||||
pub community_creation_admin_only: Option<bool>,
|
||||
pub require_email_verification: Option<bool>,
|
||||
pub require_application: Option<bool>,
|
||||
pub application_question: Option<String>,
|
||||
pub auth: String,
|
||||
}
|
||||
|
||||
|
@ -173,3 +179,40 @@ pub struct FederatedInstances {
|
|||
pub allowed: Option<Vec<String>>,
|
||||
pub blocked: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct ListRegistrationApplications {
|
||||
/// Only shows the unread applications (IE those without an admin actor)
|
||||
pub unread_only: Option<bool>,
|
||||
pub page: Option<i64>,
|
||||
pub limit: Option<i64>,
|
||||
pub auth: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct ListRegistrationApplicationsResponse {
|
||||
pub registration_applications: Vec<RegistrationApplicationView>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct ApproveRegistrationApplication {
|
||||
pub id: i32,
|
||||
pub approve: bool,
|
||||
pub deny_reason: Option<String>,
|
||||
pub auth: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct RegistrationApplicationResponse {
|
||||
pub registration_application: RegistrationApplicationView,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct GetUnreadRegistrationApplicationCount {
|
||||
pub auth: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone)]
|
||||
pub struct GetUnreadRegistrationApplicationCountResponse {
|
||||
pub registration_applications: i64,
|
||||
}
|
||||
|
|
|
@ -44,6 +44,7 @@ impl PerformCrud for GetSite {
|
|||
captcha_uuid: None,
|
||||
captcha_answer: None,
|
||||
honeypot: None,
|
||||
answer: None,
|
||||
};
|
||||
let admin_jwt = register
|
||||
.perform(context, websocket_id)
|
||||
|
@ -62,6 +63,9 @@ impl PerformCrud for GetSite {
|
|||
open_registration: setup.open_registration,
|
||||
enable_nsfw: setup.enable_nsfw,
|
||||
community_creation_admin_only: setup.community_creation_admin_only,
|
||||
require_email_verification: setup.require_email_verification,
|
||||
require_application: setup.require_application,
|
||||
application_question: setup.application_question.to_owned(),
|
||||
auth: admin_jwt,
|
||||
};
|
||||
create_site.perform(context, websocket_id).await?;
|
||||
|
|
|
@ -40,6 +40,7 @@ impl PerformCrud for EditSite {
|
|||
|
||||
let sidebar = diesel_option_overwrite(&data.sidebar);
|
||||
let description = diesel_option_overwrite(&data.description);
|
||||
let application_question = diesel_option_overwrite(&data.application_question);
|
||||
let icon = diesel_option_overwrite_to_url(&data.icon)?;
|
||||
let banner = diesel_option_overwrite_to_url(&data.banner)?;
|
||||
|
||||
|
@ -60,6 +61,8 @@ impl PerformCrud for EditSite {
|
|||
enable_nsfw: data.enable_nsfw,
|
||||
community_creation_admin_only: data.community_creation_admin_only,
|
||||
require_email_verification: data.require_email_verification,
|
||||
require_application: data.require_application,
|
||||
application_question,
|
||||
};
|
||||
|
||||
let update_site = move |conn: &'_ _| Site::update(conn, 1, &site_form);
|
||||
|
|
|
@ -21,6 +21,7 @@ use lemmy_db_schema::{
|
|||
},
|
||||
local_user::{LocalUser, LocalUserForm},
|
||||
person::{Person, PersonForm},
|
||||
registration_application::{RegistrationApplication, RegistrationApplicationForm},
|
||||
site::Site,
|
||||
},
|
||||
traits::{Crud, Followable, Joinable},
|
||||
|
@ -47,8 +48,8 @@ impl PerformCrud for Register {
|
|||
) -> Result<LoginResponse, LemmyError> {
|
||||
let data: &Register = self;
|
||||
|
||||
// no email verification if the site is not setup yet
|
||||
let mut email_verification = false;
|
||||
// no email verification, or applications if the site is not setup yet
|
||||
let (mut email_verification, mut require_application) = (false, false);
|
||||
|
||||
// Make sure site has open registration
|
||||
if let Ok(site) = blocking(context.pool(), Site::read_simple).await? {
|
||||
|
@ -56,6 +57,7 @@ impl PerformCrud for Register {
|
|||
return Err(ApiError::err_plain("registration_closed").into());
|
||||
}
|
||||
email_verification = site.require_email_verification;
|
||||
require_application = site.require_application;
|
||||
}
|
||||
|
||||
password_length_check(&data.password)?;
|
||||
|
@ -65,6 +67,10 @@ impl PerformCrud for Register {
|
|||
return Err(ApiError::err_plain("email_required").into());
|
||||
}
|
||||
|
||||
if require_application && data.answer.is_none() {
|
||||
return Err(ApiError::err_plain("registration_application_answer_required").into());
|
||||
}
|
||||
|
||||
// Make sure passwords match
|
||||
if data.password != data.password_verify {
|
||||
return Err(ApiError::err_plain("passwords_dont_match").into());
|
||||
|
@ -164,6 +170,21 @@ impl PerformCrud for Register {
|
|||
}
|
||||
};
|
||||
|
||||
if require_application {
|
||||
// Create the registration application
|
||||
let form = RegistrationApplicationForm {
|
||||
local_user_id: Some(inserted_local_user.id),
|
||||
// We already made sure answer was not null above
|
||||
answer: data.answer.to_owned(),
|
||||
..RegistrationApplicationForm::default()
|
||||
};
|
||||
|
||||
blocking(context.pool(), move |conn| {
|
||||
RegistrationApplication::create(conn, &form)
|
||||
})
|
||||
.await??;
|
||||
}
|
||||
|
||||
let main_community_keypair = generate_actor_keypair()?;
|
||||
|
||||
// Create the main community if it doesn't exist
|
||||
|
@ -243,6 +264,7 @@ impl PerformCrud for Register {
|
|||
&context.settings().hostname,
|
||||
)?)
|
||||
};
|
||||
// TODO this needs a "registration created" type response
|
||||
Ok(LoginResponse { jwt })
|
||||
}
|
||||
}
|
||||
|
|
|
@ -66,6 +66,8 @@ mod tests {
|
|||
updated: None,
|
||||
community_creation_admin_only: Some(false),
|
||||
require_email_verification: None,
|
||||
require_application: None,
|
||||
application_question: None,
|
||||
};
|
||||
|
||||
Site::create(&conn, &site_form).unwrap();
|
||||
|
|
|
@ -13,5 +13,6 @@ pub mod person_mention;
|
|||
pub mod post;
|
||||
pub mod post_report;
|
||||
pub mod private_message;
|
||||
pub mod registration_application;
|
||||
pub mod secret;
|
||||
pub mod site;
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
use crate::{source::registration_application::*, traits::Crud};
|
||||
use diesel::{insert_into, result::Error, PgConnection, QueryDsl, RunQueryDsl};
|
||||
|
||||
impl Crud for RegistrationApplication {
|
||||
type Form = RegistrationApplicationForm;
|
||||
type IdType = i32;
|
||||
fn create(conn: &PgConnection, form: &Self::Form) -> Result<Self, Error> {
|
||||
use crate::schema::registration_application::dsl::*;
|
||||
insert_into(registration_application)
|
||||
.values(form)
|
||||
.get_result::<Self>(conn)
|
||||
}
|
||||
|
||||
fn read(conn: &PgConnection, id_: Self::IdType) -> Result<Self, Error> {
|
||||
use crate::schema::registration_application::dsl::*;
|
||||
registration_application.find(id_).first::<Self>(conn)
|
||||
}
|
||||
|
||||
fn update(conn: &PgConnection, id_: Self::IdType, form: &Self::Form) -> Result<Self, Error> {
|
||||
use crate::schema::registration_application::dsl::*;
|
||||
diesel::update(registration_application.find(id_))
|
||||
.set(form)
|
||||
.get_result::<Self>(conn)
|
||||
}
|
||||
|
||||
fn delete(conn: &PgConnection, id_: Self::IdType) -> Result<usize, Error> {
|
||||
use crate::schema::registration_application::dsl::*;
|
||||
diesel::delete(registration_application.find(id_)).execute(conn)
|
||||
}
|
||||
}
|
|
@ -158,6 +158,7 @@ table! {
|
|||
show_read_posts -> Bool,
|
||||
show_new_post_notifs -> Bool,
|
||||
email_verified -> Bool,
|
||||
accepted_application -> Bool,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -449,6 +450,8 @@ table! {
|
|||
description -> Nullable<Text>,
|
||||
community_creation_admin_only -> Bool,
|
||||
require_email_verification -> Bool,
|
||||
require_application -> Bool,
|
||||
application_question -> Nullable<Text>,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -569,6 +572,17 @@ table! {
|
|||
}
|
||||
}
|
||||
|
||||
table! {
|
||||
registration_application (id) {
|
||||
id -> Int4,
|
||||
local_user_id -> Int4,
|
||||
answer -> Text,
|
||||
admin_id -> Nullable<Int4>,
|
||||
deny_reason -> Nullable<Text>,
|
||||
published -> Timestamp,
|
||||
}
|
||||
}
|
||||
|
||||
joinable!(comment_alias_1 -> person_alias_1 (creator_id));
|
||||
joinable!(comment -> comment_alias_1 (parent_id));
|
||||
joinable!(person_mention -> person_alias_1 (recipient_id));
|
||||
|
@ -631,6 +645,8 @@ joinable!(post_saved -> post (post_id));
|
|||
joinable!(site -> person (creator_id));
|
||||
joinable!(site_aggregates -> site (site_id));
|
||||
joinable!(email_verification -> local_user (local_user_id));
|
||||
joinable!(registration_application -> local_user (local_user_id));
|
||||
joinable!(registration_application -> person (admin_id));
|
||||
|
||||
allow_tables_to_appear_in_same_query!(
|
||||
activity,
|
||||
|
@ -674,5 +690,6 @@ allow_tables_to_appear_in_same_query!(
|
|||
comment_alias_1,
|
||||
person_alias_1,
|
||||
person_alias_2,
|
||||
email_verification
|
||||
email_verification,
|
||||
registration_application
|
||||
);
|
||||
|
|
|
@ -24,6 +24,7 @@ pub struct LocalUser {
|
|||
pub show_read_posts: bool,
|
||||
pub show_new_post_notifs: bool,
|
||||
pub email_verified: bool,
|
||||
pub accepted_application: bool,
|
||||
}
|
||||
|
||||
// TODO redo these, check table defaults
|
||||
|
@ -45,6 +46,7 @@ pub struct LocalUserForm {
|
|||
pub show_read_posts: Option<bool>,
|
||||
pub show_new_post_notifs: Option<bool>,
|
||||
pub email_verified: Option<bool>,
|
||||
pub accepted_application: Option<bool>,
|
||||
}
|
||||
|
||||
/// A local user view that removes password encrypted
|
||||
|
|
|
@ -13,5 +13,6 @@ pub mod person_mention;
|
|||
pub mod post;
|
||||
pub mod post_report;
|
||||
pub mod private_message;
|
||||
pub mod registration_application;
|
||||
pub mod secret;
|
||||
pub mod site;
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
use crate::{
|
||||
newtypes::{LocalUserId, PersonId},
|
||||
schema::registration_application,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Clone, Queryable, Identifiable, PartialEq, Debug, Serialize, Deserialize)]
|
||||
#[table_name = "registration_application"]
|
||||
pub struct RegistrationApplication {
|
||||
pub id: i32,
|
||||
pub local_user_id: LocalUserId,
|
||||
pub answer: String,
|
||||
pub admin_id: Option<PersonId>,
|
||||
pub deny_reason: Option<String>,
|
||||
pub published: chrono::NaiveDateTime,
|
||||
}
|
||||
|
||||
#[derive(Insertable, AsChangeset, Default)]
|
||||
#[table_name = "registration_application"]
|
||||
pub struct RegistrationApplicationForm {
|
||||
pub local_user_id: Option<LocalUserId>,
|
||||
pub answer: Option<String>,
|
||||
pub admin_id: Option<PersonId>,
|
||||
pub deny_reason: Option<Option<String>>,
|
||||
}
|
|
@ -21,6 +21,8 @@ pub struct Site {
|
|||
pub description: Option<String>,
|
||||
pub community_creation_admin_only: bool,
|
||||
pub require_email_verification: bool,
|
||||
pub require_application: bool,
|
||||
pub application_question: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Insertable, AsChangeset, Default)]
|
||||
|
@ -39,4 +41,6 @@ pub struct SiteForm {
|
|||
pub description: Option<Option<String>>,
|
||||
pub community_creation_admin_only: Option<bool>,
|
||||
pub require_email_verification: Option<bool>,
|
||||
pub require_application: Option<bool>,
|
||||
pub application_question: Option<Option<String>>,
|
||||
}
|
||||
|
|
|
@ -7,4 +7,5 @@ pub mod local_user_view;
|
|||
pub mod post_report_view;
|
||||
pub mod post_view;
|
||||
pub mod private_message_view;
|
||||
pub mod registration_application_view;
|
||||
pub mod site_view;
|
||||
|
|
|
@ -0,0 +1,378 @@
|
|||
use diesel::{dsl::count, result::Error, *};
|
||||
use lemmy_db_schema::{
|
||||
limit_and_offset,
|
||||
schema::{local_user, person, person_alias_1, registration_application},
|
||||
source::{
|
||||
local_user::LocalUser,
|
||||
person::{Person, PersonAlias1, PersonSafe, PersonSafeAlias1},
|
||||
registration_application::RegistrationApplication,
|
||||
},
|
||||
traits::{MaybeOptional, ToSafe, ViewToVec},
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
|
||||
pub struct RegistrationApplicationView {
|
||||
pub registration_application: RegistrationApplication,
|
||||
pub creator_local_user: LocalUser,
|
||||
pub creator: PersonSafe,
|
||||
pub admin: Option<PersonSafeAlias1>,
|
||||
}
|
||||
|
||||
type RegistrationApplicationViewTuple = (
|
||||
RegistrationApplication,
|
||||
LocalUser,
|
||||
PersonSafe,
|
||||
Option<PersonSafeAlias1>,
|
||||
);
|
||||
|
||||
impl RegistrationApplicationView {
|
||||
pub fn read(conn: &PgConnection, registration_application_id: i32) -> Result<Self, Error> {
|
||||
let (registration_application, creator_local_user, creator, admin) =
|
||||
registration_application::table
|
||||
.find(registration_application_id)
|
||||
.inner_join(
|
||||
local_user::table.on(registration_application::local_user_id.eq(local_user::id)),
|
||||
)
|
||||
.inner_join(person::table.on(local_user::person_id.eq(person::id)))
|
||||
.left_join(
|
||||
person_alias_1::table
|
||||
.on(registration_application::admin_id.eq(person_alias_1::id.nullable())),
|
||||
)
|
||||
.order_by(registration_application::published.desc())
|
||||
.select((
|
||||
registration_application::all_columns,
|
||||
local_user::all_columns,
|
||||
Person::safe_columns_tuple(),
|
||||
PersonAlias1::safe_columns_tuple().nullable(),
|
||||
))
|
||||
.first::<RegistrationApplicationViewTuple>(conn)?;
|
||||
|
||||
Ok(RegistrationApplicationView {
|
||||
registration_application,
|
||||
creator_local_user,
|
||||
creator,
|
||||
admin,
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns the current unread registration_application count
|
||||
pub fn get_unread_count(conn: &PgConnection, verified_email_only: bool) -> Result<i64, Error> {
|
||||
let mut query = registration_application::table
|
||||
.inner_join(local_user::table.on(registration_application::local_user_id.eq(local_user::id)))
|
||||
.inner_join(person::table.on(local_user::person_id.eq(person::id)))
|
||||
.left_join(
|
||||
person_alias_1::table
|
||||
.on(registration_application::admin_id.eq(person_alias_1::id.nullable())),
|
||||
)
|
||||
.filter(registration_application::admin_id.is_null())
|
||||
.into_boxed();
|
||||
|
||||
if verified_email_only {
|
||||
query = query.filter(local_user::email_verified.eq(true))
|
||||
}
|
||||
|
||||
query
|
||||
.select(count(registration_application::id))
|
||||
.first::<i64>(conn)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct RegistrationApplicationQueryBuilder<'a> {
|
||||
conn: &'a PgConnection,
|
||||
unread_only: Option<bool>,
|
||||
verified_email_only: Option<bool>,
|
||||
page: Option<i64>,
|
||||
limit: Option<i64>,
|
||||
}
|
||||
|
||||
impl<'a> RegistrationApplicationQueryBuilder<'a> {
|
||||
pub fn create(conn: &'a PgConnection) -> Self {
|
||||
RegistrationApplicationQueryBuilder {
|
||||
conn,
|
||||
unread_only: None,
|
||||
verified_email_only: None,
|
||||
page: None,
|
||||
limit: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn unread_only<T: MaybeOptional<bool>>(mut self, unread_only: T) -> Self {
|
||||
self.unread_only = unread_only.get_optional();
|
||||
self
|
||||
}
|
||||
|
||||
pub fn verified_email_only<T: MaybeOptional<bool>>(mut self, verified_email_only: T) -> Self {
|
||||
self.verified_email_only = verified_email_only.get_optional();
|
||||
self
|
||||
}
|
||||
|
||||
pub fn page<T: MaybeOptional<i64>>(mut self, page: T) -> Self {
|
||||
self.page = page.get_optional();
|
||||
self
|
||||
}
|
||||
|
||||
pub fn limit<T: MaybeOptional<i64>>(mut self, limit: T) -> Self {
|
||||
self.limit = limit.get_optional();
|
||||
self
|
||||
}
|
||||
|
||||
pub fn list(self) -> Result<Vec<RegistrationApplicationView>, Error> {
|
||||
let mut query = registration_application::table
|
||||
.inner_join(local_user::table.on(registration_application::local_user_id.eq(local_user::id)))
|
||||
.inner_join(person::table.on(local_user::person_id.eq(person::id)))
|
||||
.left_join(
|
||||
person_alias_1::table
|
||||
.on(registration_application::admin_id.eq(person_alias_1::id.nullable())),
|
||||
)
|
||||
.order_by(registration_application::published.desc())
|
||||
.select((
|
||||
registration_application::all_columns,
|
||||
local_user::all_columns,
|
||||
Person::safe_columns_tuple(),
|
||||
PersonAlias1::safe_columns_tuple().nullable(),
|
||||
))
|
||||
.into_boxed();
|
||||
|
||||
if self.unread_only.unwrap_or(false) {
|
||||
query = query.filter(registration_application::admin_id.is_null())
|
||||
}
|
||||
|
||||
if self.verified_email_only.unwrap_or(false) {
|
||||
query = query.filter(local_user::email_verified.eq(true))
|
||||
}
|
||||
|
||||
let (limit, offset) = limit_and_offset(self.page, self.limit);
|
||||
|
||||
query = query
|
||||
.limit(limit)
|
||||
.offset(offset)
|
||||
.order_by(registration_application::published.desc());
|
||||
|
||||
let res = query.load::<RegistrationApplicationViewTuple>(self.conn)?;
|
||||
|
||||
Ok(RegistrationApplicationView::from_tuple_to_vec(res))
|
||||
}
|
||||
}
|
||||
|
||||
impl ViewToVec for RegistrationApplicationView {
|
||||
type DbTuple = RegistrationApplicationViewTuple;
|
||||
fn from_tuple_to_vec(items: Vec<Self::DbTuple>) -> Vec<Self> {
|
||||
items
|
||||
.iter()
|
||||
.map(|a| Self {
|
||||
registration_application: a.0.to_owned(),
|
||||
creator_local_user: a.1.to_owned(),
|
||||
creator: a.2.to_owned(),
|
||||
admin: a.3.to_owned(),
|
||||
})
|
||||
.collect::<Vec<Self>>()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::registration_application_view::{
|
||||
RegistrationApplicationQueryBuilder,
|
||||
RegistrationApplicationView,
|
||||
};
|
||||
use lemmy_db_schema::{
|
||||
establish_unpooled_connection,
|
||||
source::{
|
||||
local_user::{LocalUser, LocalUserForm},
|
||||
person::*,
|
||||
registration_application::{RegistrationApplication, RegistrationApplicationForm},
|
||||
},
|
||||
traits::Crud,
|
||||
};
|
||||
use serial_test::serial;
|
||||
|
||||
#[test]
|
||||
#[serial]
|
||||
fn test_crud() {
|
||||
let conn = establish_unpooled_connection();
|
||||
|
||||
let timmy_person_form = PersonForm {
|
||||
name: "timmy_rav".into(),
|
||||
admin: Some(true),
|
||||
..PersonForm::default()
|
||||
};
|
||||
|
||||
let inserted_timmy_person = Person::create(&conn, &timmy_person_form).unwrap();
|
||||
|
||||
let timmy_local_user_form = LocalUserForm {
|
||||
person_id: Some(inserted_timmy_person.id),
|
||||
password_encrypted: Some("nada".to_string()),
|
||||
..LocalUserForm::default()
|
||||
};
|
||||
|
||||
let _inserted_timmy_local_user = LocalUser::create(&conn, &timmy_local_user_form).unwrap();
|
||||
|
||||
let sara_person_form = PersonForm {
|
||||
name: "sara_rav".into(),
|
||||
..PersonForm::default()
|
||||
};
|
||||
|
||||
let inserted_sara_person = Person::create(&conn, &sara_person_form).unwrap();
|
||||
|
||||
let sara_local_user_form = LocalUserForm {
|
||||
person_id: Some(inserted_sara_person.id),
|
||||
password_encrypted: Some("nada".to_string()),
|
||||
..LocalUserForm::default()
|
||||
};
|
||||
|
||||
let inserted_sara_local_user = LocalUser::create(&conn, &sara_local_user_form).unwrap();
|
||||
|
||||
// Sara creates an application
|
||||
let sara_app_form = RegistrationApplicationForm {
|
||||
local_user_id: Some(inserted_sara_local_user.id),
|
||||
answer: Some("LET ME IIIIINN".to_string()),
|
||||
..RegistrationApplicationForm::default()
|
||||
};
|
||||
|
||||
let sara_app = RegistrationApplication::create(&conn, &sara_app_form).unwrap();
|
||||
|
||||
let read_sara_app_view = RegistrationApplicationView::read(&conn, sara_app.id).unwrap();
|
||||
|
||||
let jess_person_form = PersonForm {
|
||||
name: "jess_rav".into(),
|
||||
..PersonForm::default()
|
||||
};
|
||||
|
||||
let inserted_jess_person = Person::create(&conn, &jess_person_form).unwrap();
|
||||
|
||||
let jess_local_user_form = LocalUserForm {
|
||||
person_id: Some(inserted_jess_person.id),
|
||||
password_encrypted: Some("nada".to_string()),
|
||||
..LocalUserForm::default()
|
||||
};
|
||||
|
||||
let inserted_jess_local_user = LocalUser::create(&conn, &jess_local_user_form).unwrap();
|
||||
|
||||
// Sara creates an application
|
||||
let jess_app_form = RegistrationApplicationForm {
|
||||
local_user_id: Some(inserted_jess_local_user.id),
|
||||
answer: Some("LET ME IIIIINN".to_string()),
|
||||
..RegistrationApplicationForm::default()
|
||||
};
|
||||
|
||||
let jess_app = RegistrationApplication::create(&conn, &jess_app_form).unwrap();
|
||||
|
||||
let read_jess_app_view = RegistrationApplicationView::read(&conn, jess_app.id).unwrap();
|
||||
|
||||
let mut expected_sara_app_view = RegistrationApplicationView {
|
||||
registration_application: sara_app.to_owned(),
|
||||
creator_local_user: inserted_sara_local_user.to_owned(),
|
||||
creator: PersonSafe {
|
||||
id: inserted_sara_person.id,
|
||||
name: inserted_sara_person.name.to_owned(),
|
||||
display_name: None,
|
||||
published: inserted_sara_person.published,
|
||||
avatar: None,
|
||||
actor_id: inserted_sara_person.actor_id.to_owned(),
|
||||
local: true,
|
||||
banned: false,
|
||||
deleted: false,
|
||||
admin: false,
|
||||
bot_account: false,
|
||||
bio: None,
|
||||
banner: None,
|
||||
updated: None,
|
||||
inbox_url: inserted_sara_person.inbox_url.to_owned(),
|
||||
shared_inbox_url: None,
|
||||
matrix_user_id: None,
|
||||
},
|
||||
admin: None,
|
||||
};
|
||||
|
||||
assert_eq!(read_sara_app_view, expected_sara_app_view);
|
||||
|
||||
// Do a batch read of the applications
|
||||
let apps = RegistrationApplicationQueryBuilder::create(&conn)
|
||||
.unread_only(true)
|
||||
.list()
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
apps,
|
||||
[
|
||||
read_jess_app_view.to_owned(),
|
||||
expected_sara_app_view.to_owned()
|
||||
]
|
||||
);
|
||||
|
||||
// Make sure the counts are correct
|
||||
let unread_count = RegistrationApplicationView::get_unread_count(&conn, false).unwrap();
|
||||
assert_eq!(unread_count, 2);
|
||||
|
||||
// Approve the application
|
||||
let approve_form = RegistrationApplicationForm {
|
||||
admin_id: Some(inserted_timmy_person.id),
|
||||
deny_reason: None,
|
||||
..RegistrationApplicationForm::default()
|
||||
};
|
||||
|
||||
RegistrationApplication::update(&conn, sara_app.id, &approve_form).unwrap();
|
||||
|
||||
// Update the local_user row
|
||||
let approve_local_user_form = LocalUserForm {
|
||||
accepted_application: Some(true),
|
||||
..LocalUserForm::default()
|
||||
};
|
||||
|
||||
LocalUser::update(&conn, inserted_sara_local_user.id, &approve_local_user_form).unwrap();
|
||||
|
||||
let read_sara_app_view_after_approve =
|
||||
RegistrationApplicationView::read(&conn, sara_app.id).unwrap();
|
||||
|
||||
// Make sure the columns changed
|
||||
expected_sara_app_view
|
||||
.creator_local_user
|
||||
.accepted_application = true;
|
||||
expected_sara_app_view.registration_application.admin_id = Some(inserted_timmy_person.id);
|
||||
|
||||
expected_sara_app_view.admin = Some(PersonSafeAlias1 {
|
||||
id: inserted_timmy_person.id,
|
||||
name: inserted_timmy_person.name.to_owned(),
|
||||
display_name: None,
|
||||
published: inserted_timmy_person.published,
|
||||
avatar: None,
|
||||
actor_id: inserted_timmy_person.actor_id.to_owned(),
|
||||
local: true,
|
||||
banned: false,
|
||||
deleted: false,
|
||||
admin: true,
|
||||
bot_account: false,
|
||||
bio: None,
|
||||
banner: None,
|
||||
updated: None,
|
||||
inbox_url: inserted_timmy_person.inbox_url.to_owned(),
|
||||
shared_inbox_url: None,
|
||||
matrix_user_id: None,
|
||||
});
|
||||
assert_eq!(read_sara_app_view_after_approve, expected_sara_app_view);
|
||||
|
||||
// Do a batch read of apps again
|
||||
// It should show only jessicas which is unresolved
|
||||
let apps_after_resolve = RegistrationApplicationQueryBuilder::create(&conn)
|
||||
.unread_only(true)
|
||||
.list()
|
||||
.unwrap();
|
||||
assert_eq!(apps_after_resolve, vec![read_jess_app_view]);
|
||||
|
||||
// Make sure the counts are correct
|
||||
let unread_count_after_approve =
|
||||
RegistrationApplicationView::get_unread_count(&conn, false).unwrap();
|
||||
assert_eq!(unread_count_after_approve, 1);
|
||||
|
||||
// Make sure the not undenied_only has all the apps
|
||||
let all_apps = RegistrationApplicationQueryBuilder::create(&conn)
|
||||
.list()
|
||||
.unwrap();
|
||||
assert_eq!(all_apps.len(), 2);
|
||||
|
||||
Person::delete(&conn, inserted_timmy_person.id).unwrap();
|
||||
Person::delete(&conn, inserted_sara_person.id).unwrap();
|
||||
Person::delete(&conn, inserted_jess_person.id).unwrap();
|
||||
}
|
||||
}
|
|
@ -187,4 +187,10 @@ pub struct SetupConfig {
|
|||
pub enable_nsfw: Option<bool>,
|
||||
#[default(None)]
|
||||
pub community_creation_admin_only: Option<bool>,
|
||||
#[default(None)]
|
||||
pub require_email_verification: Option<bool>,
|
||||
#[default(None)]
|
||||
pub require_application: Option<bool>,
|
||||
#[default(None)]
|
||||
pub application_question: Option<String>,
|
||||
}
|
||||
|
|
|
@ -126,6 +126,9 @@ pub enum UserOperation {
|
|||
BanFromCommunity,
|
||||
AddModToCommunity,
|
||||
AddAdmin,
|
||||
GetUnreadRegistrationApplicationCount,
|
||||
ListRegistrationApplications,
|
||||
ApproveRegistrationApplication,
|
||||
BanPerson,
|
||||
Search,
|
||||
ResolveObject,
|
||||
|
|
|
@ -1,11 +1,8 @@
|
|||
-- This file should undo anything in `up.sql`
|
||||
-- Add columns to site table
|
||||
alter table site drop column require_application;
|
||||
alter table site drop column require_email;
|
||||
alter table site drop column application_question;
|
||||
|
||||
-- Add pending to local_user
|
||||
alter table local_user drop column accepted_application;
|
||||
alter table local_user drop column verified_email;
|
||||
|
||||
drop table registration_application;
|
||||
|
|
|
@ -1,19 +1,18 @@
|
|||
-- Add columns to site table
|
||||
alter table site add column require_application boolean not null default false;
|
||||
alter table site add column require_email boolean not null default false;
|
||||
alter table site add column application_question text;
|
||||
|
||||
-- Add pending to local_user
|
||||
alter table local_user add column accepted_application boolean not null default false;
|
||||
alter table local_user add column verified_email boolean not null default false;
|
||||
|
||||
create table registration_application (
|
||||
id serial primary key,
|
||||
local_user_id int references local_user on update cascade on delete cascade not null,
|
||||
answer text not null,
|
||||
acceptor_id int references person on update cascade on delete cascade,
|
||||
accepted boolean not null default false,
|
||||
admin_id int references person on update cascade on delete cascade,
|
||||
deny_reason text,
|
||||
published timestamp not null default now(),
|
||||
unique(local_user_id)
|
||||
);
|
||||
|
||||
create index idx_registration_application_published on registration_application (published desc);
|
||||
|
|
|
@ -211,9 +211,21 @@ pub fn config(cfg: &mut web::ServiceConfig, rate_limit: &RateLimit) {
|
|||
)
|
||||
// Admin Actions
|
||||
.service(
|
||||
web::resource("/admin/add")
|
||||
web::scope("/admin")
|
||||
.wrap(rate_limit.message())
|
||||
.route(web::post().to(route_post::<AddAdmin>)),
|
||||
.route("/add", web::post().to(route_post::<AddAdmin>))
|
||||
.route(
|
||||
"/registration_application/count",
|
||||
web::get().to(route_get::<GetUnreadRegistrationApplicationCount>),
|
||||
)
|
||||
.route(
|
||||
"/registration_application/list",
|
||||
web::get().to(route_get::<ListRegistrationApplications>),
|
||||
)
|
||||
.route(
|
||||
"/registration_application/approve",
|
||||
web::put().to(route_post::<ApproveRegistrationApplication>),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue