mirror of
https://github.com/LemmyNet/lemmy.git
synced 2024-12-22 19:01:32 +00:00
Adding local site settings to reject federated upvotes or downvotes. (#5038)
* Adding local site settings to reject federated upvotes or downvotes. - Should help defend against downvote spamming instances. - Fixes #4086 * Adding new vote mode types. * Simpler activitypub vote check. * Adding undo vote for failed vote mode check. * Update crates/api_common/src/utils.rs --------- Co-authored-by: Nutomic <me@nutomic.com>
This commit is contained in:
parent
e3edc317be
commit
ffb94fde85
13 changed files with 217 additions and 39 deletions
|
@ -5,7 +5,7 @@ use lemmy_api_common::{
|
|||
comment::{CommentResponse, CreateCommentLike},
|
||||
context::LemmyContext,
|
||||
send_activity::{ActivityChannel, SendActivityData},
|
||||
utils::{check_bot_account, check_community_user_action, check_downvotes_enabled},
|
||||
utils::{check_bot_account, check_community_user_action, check_local_vote_mode, VoteItem},
|
||||
};
|
||||
use lemmy_db_schema::{
|
||||
newtypes::LocalUserId,
|
||||
|
@ -27,14 +27,20 @@ pub async fn like_comment(
|
|||
local_user_view: LocalUserView,
|
||||
) -> LemmyResult<Json<CommentResponse>> {
|
||||
let local_site = LocalSite::read(&mut context.pool()).await?;
|
||||
let comment_id = data.comment_id;
|
||||
|
||||
let mut recipient_ids = Vec::<LocalUserId>::new();
|
||||
|
||||
// Don't do a downvote if site has downvotes disabled
|
||||
check_downvotes_enabled(data.score, &local_site)?;
|
||||
check_local_vote_mode(
|
||||
data.score,
|
||||
VoteItem::Comment(comment_id),
|
||||
&local_site,
|
||||
local_user_view.person.id,
|
||||
&mut context.pool(),
|
||||
)
|
||||
.await?;
|
||||
check_bot_account(&local_user_view.person)?;
|
||||
|
||||
let comment_id = data.comment_id;
|
||||
let orig_comment = CommentView::read(
|
||||
&mut context.pool(),
|
||||
comment_id,
|
||||
|
|
|
@ -8,8 +8,9 @@ use lemmy_api_common::{
|
|||
utils::{
|
||||
check_bot_account,
|
||||
check_community_user_action,
|
||||
check_downvotes_enabled,
|
||||
check_local_vote_mode,
|
||||
mark_post_as_read,
|
||||
VoteItem,
|
||||
},
|
||||
};
|
||||
use lemmy_db_schema::{
|
||||
|
@ -31,13 +32,19 @@ pub async fn like_post(
|
|||
local_user_view: LocalUserView,
|
||||
) -> LemmyResult<Json<PostResponse>> {
|
||||
let local_site = LocalSite::read(&mut context.pool()).await?;
|
||||
let post_id = data.post_id;
|
||||
|
||||
// Don't do a downvote if site has downvotes disabled
|
||||
check_downvotes_enabled(data.score, &local_site)?;
|
||||
check_local_vote_mode(
|
||||
data.score,
|
||||
VoteItem::Post(post_id),
|
||||
&local_site,
|
||||
local_user_view.person.id,
|
||||
&mut context.pool(),
|
||||
)
|
||||
.await?;
|
||||
check_bot_account(&local_user_view.person)?;
|
||||
|
||||
// Check for a community ban
|
||||
let post_id = data.post_id;
|
||||
let post = Post::read(&mut context.pool(), post_id).await?;
|
||||
|
||||
check_community_user_action(
|
||||
|
|
|
@ -21,6 +21,7 @@ use lemmy_db_schema::{
|
|||
tagline::Tagline,
|
||||
},
|
||||
CommentSortType,
|
||||
FederationMode,
|
||||
ListingType,
|
||||
ModlogActionType,
|
||||
PostListingMode,
|
||||
|
@ -170,7 +171,6 @@ pub struct CreateSite {
|
|||
pub description: Option<String>,
|
||||
pub icon: Option<String>,
|
||||
pub banner: Option<String>,
|
||||
pub enable_downvotes: Option<bool>,
|
||||
pub enable_nsfw: Option<bool>,
|
||||
pub community_creation_admin_only: Option<bool>,
|
||||
pub require_email_verification: Option<bool>,
|
||||
|
@ -208,6 +208,10 @@ pub struct CreateSite {
|
|||
pub registration_mode: Option<RegistrationMode>,
|
||||
pub oauth_registration: Option<bool>,
|
||||
pub content_warning: Option<String>,
|
||||
pub post_upvotes: Option<FederationMode>,
|
||||
pub post_downvotes: Option<FederationMode>,
|
||||
pub comment_upvotes: Option<FederationMode>,
|
||||
pub comment_downvotes: Option<FederationMode>,
|
||||
}
|
||||
|
||||
#[skip_serializing_none]
|
||||
|
@ -224,8 +228,6 @@ pub struct EditSite {
|
|||
pub icon: Option<String>,
|
||||
/// A url for your site's banner.
|
||||
pub banner: Option<String>,
|
||||
/// Whether to enable downvotes.
|
||||
pub enable_downvotes: Option<bool>,
|
||||
/// Whether to enable NSFW.
|
||||
pub enable_nsfw: Option<bool>,
|
||||
/// Limits community creation to admins only.
|
||||
|
@ -291,13 +293,21 @@ pub struct EditSite {
|
|||
/// A list of blocked URLs
|
||||
pub blocked_urls: Option<Vec<String>>,
|
||||
pub registration_mode: Option<RegistrationMode>,
|
||||
/// Whether or not external auth methods can auto-register users.
|
||||
pub oauth_registration: Option<bool>,
|
||||
/// Whether to email admins for new reports.
|
||||
pub reports_email_admins: Option<bool>,
|
||||
/// If present, nsfw content is visible by default. Should be displayed by frontends/clients
|
||||
/// when the site is first opened by a user.
|
||||
pub content_warning: Option<String>,
|
||||
/// Whether or not external auth methods can auto-register users.
|
||||
pub oauth_registration: Option<bool>,
|
||||
/// What kind of post upvotes your site allows.
|
||||
pub post_upvotes: Option<FederationMode>,
|
||||
/// What kind of post downvotes your site allows.
|
||||
pub post_downvotes: Option<FederationMode>,
|
||||
/// What kind of comment upvotes your site allows.
|
||||
pub comment_upvotes: Option<FederationMode>,
|
||||
/// What kind of comment downvotes your site allows.
|
||||
pub comment_downvotes: Option<FederationMode>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
|
|
|
@ -13,7 +13,7 @@ use lemmy_db_schema::{
|
|||
aggregates::structs::{PersonPostAggregates, PersonPostAggregatesForm},
|
||||
newtypes::{CommentId, CommunityId, DbUrl, InstanceId, PersonId, PostId},
|
||||
source::{
|
||||
comment::{Comment, CommentUpdateForm},
|
||||
comment::{Comment, CommentLike, CommentUpdateForm},
|
||||
community::{Community, CommunityModerator, CommunityUpdateForm},
|
||||
community_block::CommunityBlock,
|
||||
email_verification::{EmailVerification, EmailVerificationForm},
|
||||
|
@ -28,12 +28,13 @@ use lemmy_db_schema::{
|
|||
password_reset_request::PasswordResetRequest,
|
||||
person::{Person, PersonUpdateForm},
|
||||
person_block::PersonBlock,
|
||||
post::{Post, PostRead},
|
||||
post::{Post, PostLike, PostRead},
|
||||
registration_application::RegistrationApplication,
|
||||
site::Site,
|
||||
},
|
||||
traits::Crud,
|
||||
traits::{Crud, Likeable},
|
||||
utils::DbPool,
|
||||
FederationMode,
|
||||
RegistrationMode,
|
||||
};
|
||||
use lemmy_db_views::{
|
||||
|
@ -297,13 +298,36 @@ pub async fn check_person_instance_community_block(
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// A vote item type used to check the vote mode.
|
||||
pub enum VoteItem {
|
||||
Post(PostId),
|
||||
Comment(CommentId),
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip_all)]
|
||||
pub fn check_downvotes_enabled(score: i16, local_site: &LocalSite) -> LemmyResult<()> {
|
||||
if score == -1 && !local_site.enable_downvotes {
|
||||
Err(LemmyErrorType::DownvotesAreDisabled)?
|
||||
} else {
|
||||
Ok(())
|
||||
pub async fn check_local_vote_mode(
|
||||
score: i16,
|
||||
vote_item: VoteItem,
|
||||
local_site: &LocalSite,
|
||||
person_id: PersonId,
|
||||
pool: &mut DbPool<'_>,
|
||||
) -> LemmyResult<()> {
|
||||
let (downvote_setting, upvote_setting) = match vote_item {
|
||||
VoteItem::Post(_) => (local_site.post_downvotes, local_site.post_upvotes),
|
||||
VoteItem::Comment(_) => (local_site.comment_downvotes, local_site.comment_upvotes),
|
||||
};
|
||||
|
||||
let downvote_fail = score == -1 && downvote_setting == FederationMode::Disable;
|
||||
let upvote_fail = score == 1 && upvote_setting == FederationMode::Disable;
|
||||
|
||||
// Undo previous vote for item if new vote fails
|
||||
if downvote_fail || upvote_fail {
|
||||
match vote_item {
|
||||
VoteItem::Post(post_id) => PostLike::remove(pool, person_id, post_id).await?,
|
||||
VoteItem::Comment(comment_id) => CommentLike::remove(pool, person_id, comment_id).await?,
|
||||
};
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Dont allow bots to do certain actions, like voting
|
||||
|
|
|
@ -90,7 +90,6 @@ pub async fn create_site(
|
|||
let local_site_form = LocalSiteUpdateForm {
|
||||
// Set the site setup to true
|
||||
site_setup: Some(true),
|
||||
enable_downvotes: data.enable_downvotes,
|
||||
registration_mode: data.registration_mode,
|
||||
community_creation_admin_only: data.community_creation_admin_only,
|
||||
require_email_verification: data.require_email_verification,
|
||||
|
@ -110,6 +109,10 @@ pub async fn create_site(
|
|||
captcha_enabled: data.captcha_enabled,
|
||||
captcha_difficulty: data.captcha_difficulty.clone(),
|
||||
default_post_listing_mode: data.default_post_listing_mode,
|
||||
post_upvotes: data.post_upvotes,
|
||||
post_downvotes: data.post_downvotes,
|
||||
comment_upvotes: data.comment_upvotes,
|
||||
comment_downvotes: data.comment_downvotes,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
|
|
|
@ -99,7 +99,6 @@ pub async fn update_site(
|
|||
.ok();
|
||||
|
||||
let local_site_form = LocalSiteUpdateForm {
|
||||
enable_downvotes: data.enable_downvotes,
|
||||
registration_mode: data.registration_mode,
|
||||
community_creation_admin_only: data.community_creation_admin_only,
|
||||
require_email_verification: data.require_email_verification,
|
||||
|
@ -121,6 +120,10 @@ pub async fn update_site(
|
|||
reports_email_admins: data.reports_email_admins,
|
||||
default_post_listing_mode: data.default_post_listing_mode,
|
||||
oauth_registration: data.oauth_registration,
|
||||
post_upvotes: data.post_upvotes,
|
||||
post_downvotes: data.post_downvotes,
|
||||
comment_upvotes: data.comment_upvotes,
|
||||
comment_downvotes: data.comment_downvotes,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
|
|
|
@ -18,7 +18,7 @@ use activitypub_federation::{
|
|||
traits::{ActivityHandler, Actor},
|
||||
};
|
||||
use lemmy_api_common::{context::LemmyContext, utils::check_bot_account};
|
||||
use lemmy_db_schema::source::local_site::LocalSite;
|
||||
use lemmy_db_schema::{source::local_site::LocalSite, FederationMode};
|
||||
use lemmy_utils::error::{LemmyError, LemmyResult};
|
||||
use url::Url;
|
||||
|
||||
|
@ -68,12 +68,22 @@ impl ActivityHandler for Vote {
|
|||
|
||||
check_bot_account(&actor.0)?;
|
||||
|
||||
let enable_downvotes = LocalSite::read(&mut context.pool())
|
||||
// Check for enabled federation votes
|
||||
let local_site = LocalSite::read(&mut context.pool())
|
||||
.await
|
||||
.map(|l| l.enable_downvotes)
|
||||
.unwrap_or(true);
|
||||
if self.kind == VoteType::Dislike && !enable_downvotes {
|
||||
// If this is a downvote but downvotes are ignored, only undo any existing vote
|
||||
.unwrap_or_default();
|
||||
|
||||
let (downvote_setting, upvote_setting) = match object {
|
||||
PostOrComment::Post(_) => (local_site.post_downvotes, local_site.post_upvotes),
|
||||
PostOrComment::Comment(_) => (local_site.comment_downvotes, local_site.comment_upvotes),
|
||||
};
|
||||
|
||||
// Don't allow dislikes for either disabled, or local only votes
|
||||
let downvote_fail = self.kind == VoteType::Dislike && downvote_setting != FederationMode::All;
|
||||
let upvote_fail = self.kind == VoteType::Like && upvote_setting != FederationMode::All;
|
||||
|
||||
if downvote_fail || upvote_fail {
|
||||
// If this is a rejection, undo the vote
|
||||
match object {
|
||||
PostOrComment::Post(p) => undo_vote_post(actor, &p, context).await,
|
||||
PostOrComment::Comment(c) => undo_vote_comment(actor, &c, context).await,
|
||||
|
|
|
@ -251,6 +251,27 @@ pub enum CommunityVisibility {
|
|||
LocalOnly,
|
||||
}
|
||||
|
||||
#[derive(
|
||||
EnumString, Display, Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Default, Hash,
|
||||
)]
|
||||
#[cfg_attr(feature = "full", derive(DbEnum, TS))]
|
||||
#[cfg_attr(
|
||||
feature = "full",
|
||||
ExistingTypePath = "crate::schema::sql_types::FederationModeEnum"
|
||||
)]
|
||||
#[cfg_attr(feature = "full", DbValueStyle = "verbatim")]
|
||||
#[cfg_attr(feature = "full", ts(export))]
|
||||
/// The federation mode for an item
|
||||
pub enum FederationMode {
|
||||
#[default]
|
||||
/// Allows all
|
||||
All,
|
||||
/// Allows only local
|
||||
Local,
|
||||
/// Disables
|
||||
Disable,
|
||||
}
|
||||
|
||||
/// Wrapper for assert_eq! macro. Checks that vec matches the given length, and prints the
|
||||
/// vec on failure.
|
||||
#[macro_export]
|
||||
|
|
|
@ -13,6 +13,10 @@ pub mod sql_types {
|
|||
#[diesel(postgres_type(name = "community_visibility"))]
|
||||
pub struct CommunityVisibility;
|
||||
|
||||
#[derive(diesel::sql_types::SqlType)]
|
||||
#[diesel(postgres_type(name = "federation_mode_enum"))]
|
||||
pub struct FederationModeEnum;
|
||||
|
||||
#[derive(diesel::sql_types::SqlType)]
|
||||
#[diesel(postgres_type(name = "listing_type_enum"))]
|
||||
pub struct ListingTypeEnum;
|
||||
|
@ -368,12 +372,12 @@ diesel::table! {
|
|||
use super::sql_types::PostListingModeEnum;
|
||||
use super::sql_types::PostSortTypeEnum;
|
||||
use super::sql_types::CommentSortTypeEnum;
|
||||
use super::sql_types::FederationModeEnum;
|
||||
|
||||
local_site (id) {
|
||||
id -> Int4,
|
||||
site_id -> Int4,
|
||||
site_setup -> Bool,
|
||||
enable_downvotes -> Bool,
|
||||
community_creation_admin_only -> Bool,
|
||||
require_email_verification -> Bool,
|
||||
application_question -> Nullable<Text>,
|
||||
|
@ -398,6 +402,10 @@ diesel::table! {
|
|||
default_post_sort_type -> PostSortTypeEnum,
|
||||
default_comment_sort_type -> CommentSortTypeEnum,
|
||||
oauth_registration -> Bool,
|
||||
post_upvotes -> FederationModeEnum,
|
||||
post_downvotes -> FederationModeEnum,
|
||||
comment_upvotes -> FederationModeEnum,
|
||||
comment_downvotes -> FederationModeEnum,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@ use crate::schema::local_site;
|
|||
use crate::{
|
||||
newtypes::{LocalSiteId, SiteId},
|
||||
CommentSortType,
|
||||
FederationMode,
|
||||
ListingType,
|
||||
PostListingMode,
|
||||
PostSortType,
|
||||
|
@ -27,8 +28,6 @@ pub struct LocalSite {
|
|||
pub site_id: SiteId,
|
||||
/// True if the site is set up.
|
||||
pub site_setup: bool,
|
||||
/// Whether downvotes are enabled.
|
||||
pub enable_downvotes: bool,
|
||||
/// Whether only admins can create communities.
|
||||
pub community_creation_admin_only: bool,
|
||||
/// Whether emails are required.
|
||||
|
@ -72,6 +71,14 @@ pub struct LocalSite {
|
|||
pub default_comment_sort_type: CommentSortType,
|
||||
/// Whether or not external auth methods can auto-register users.
|
||||
pub oauth_registration: bool,
|
||||
/// What kind of post upvotes your site allows.
|
||||
pub post_upvotes: FederationMode,
|
||||
/// What kind of post downvotes your site allows.
|
||||
pub post_downvotes: FederationMode,
|
||||
/// What kind of comment upvotes your site allows.
|
||||
pub comment_upvotes: FederationMode,
|
||||
/// What kind of comment downvotes your site allows.
|
||||
pub comment_downvotes: FederationMode,
|
||||
}
|
||||
|
||||
#[derive(Clone, derive_new::new)]
|
||||
|
@ -82,8 +89,6 @@ pub struct LocalSiteInsertForm {
|
|||
#[new(default)]
|
||||
pub site_setup: Option<bool>,
|
||||
#[new(default)]
|
||||
pub enable_downvotes: Option<bool>,
|
||||
#[new(default)]
|
||||
pub community_creation_admin_only: Option<bool>,
|
||||
#[new(default)]
|
||||
pub require_email_verification: Option<bool>,
|
||||
|
@ -114,8 +119,6 @@ pub struct LocalSiteInsertForm {
|
|||
#[new(default)]
|
||||
pub registration_mode: Option<RegistrationMode>,
|
||||
#[new(default)]
|
||||
pub oauth_registration: Option<bool>,
|
||||
#[new(default)]
|
||||
pub reports_email_admins: Option<bool>,
|
||||
#[new(default)]
|
||||
pub federation_signed_fetch: Option<bool>,
|
||||
|
@ -125,6 +128,16 @@ pub struct LocalSiteInsertForm {
|
|||
pub default_post_sort_type: Option<PostSortType>,
|
||||
#[new(default)]
|
||||
pub default_comment_sort_type: Option<CommentSortType>,
|
||||
#[new(default)]
|
||||
pub oauth_registration: Option<bool>,
|
||||
#[new(default)]
|
||||
pub post_upvotes: Option<FederationMode>,
|
||||
#[new(default)]
|
||||
pub post_downvotes: Option<FederationMode>,
|
||||
#[new(default)]
|
||||
pub comment_upvotes: Option<FederationMode>,
|
||||
#[new(default)]
|
||||
pub comment_downvotes: Option<FederationMode>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
|
@ -132,7 +145,6 @@ pub struct LocalSiteInsertForm {
|
|||
#[cfg_attr(feature = "full", diesel(table_name = local_site))]
|
||||
pub struct LocalSiteUpdateForm {
|
||||
pub site_setup: Option<bool>,
|
||||
pub enable_downvotes: Option<bool>,
|
||||
pub community_creation_admin_only: Option<bool>,
|
||||
pub require_email_verification: Option<bool>,
|
||||
pub application_question: Option<Option<String>>,
|
||||
|
@ -148,11 +160,15 @@ pub struct LocalSiteUpdateForm {
|
|||
pub captcha_enabled: Option<bool>,
|
||||
pub captcha_difficulty: Option<String>,
|
||||
pub registration_mode: Option<RegistrationMode>,
|
||||
pub oauth_registration: Option<bool>,
|
||||
pub reports_email_admins: Option<bool>,
|
||||
pub updated: Option<Option<DateTime<Utc>>>,
|
||||
pub federation_signed_fetch: Option<bool>,
|
||||
pub default_post_listing_mode: Option<PostListingMode>,
|
||||
pub default_post_sort_type: Option<PostSortType>,
|
||||
pub default_comment_sort_type: Option<CommentSortType>,
|
||||
pub oauth_registration: Option<bool>,
|
||||
pub post_upvotes: Option<FederationMode>,
|
||||
pub post_downvotes: Option<FederationMode>,
|
||||
pub comment_upvotes: Option<FederationMode>,
|
||||
pub comment_downvotes: Option<FederationMode>,
|
||||
}
|
||||
|
|
|
@ -46,7 +46,7 @@ pub enum LemmyErrorType {
|
|||
PersonIsBlocked,
|
||||
CommunityIsBlocked,
|
||||
InstanceIsBlocked,
|
||||
DownvotesAreDisabled,
|
||||
VoteNotAllowed,
|
||||
InstanceIsPrivate,
|
||||
/// Password must be between 10 and 60 characters
|
||||
InvalidPassword,
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
-- Add back the enable_downvotes column
|
||||
ALTER TABLE local_site
|
||||
ADD COLUMN enable_downvotes boolean DEFAULT TRUE NOT NULL;
|
||||
|
||||
-- regenerate their values (from post_downvotes alone)
|
||||
WITH subquery AS (
|
||||
SELECT
|
||||
post_downvotes,
|
||||
CASE WHEN post_downvotes = 'Disable'::federation_mode_enum THEN
|
||||
FALSE
|
||||
ELSE
|
||||
TRUE
|
||||
END
|
||||
FROM
|
||||
local_site)
|
||||
UPDATE
|
||||
local_site
|
||||
SET
|
||||
enable_downvotes = subquery.case
|
||||
FROM
|
||||
subquery;
|
||||
|
||||
-- Drop the new columns
|
||||
ALTER TABLE local_site
|
||||
DROP COLUMN post_upvotes,
|
||||
DROP COLUMN post_downvotes,
|
||||
DROP COLUMN comment_upvotes,
|
||||
DROP COLUMN comment_downvotes;
|
||||
|
||||
DROP TYPE federation_mode_enum;
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
-- This removes the simple enable_downvotes setting, in favor of an
|
||||
-- expanded federation mode type for post/comment up/downvotes.
|
||||
-- Create the federation mode enum
|
||||
CREATE TYPE federation_mode_enum AS ENUM (
|
||||
'All',
|
||||
'Local',
|
||||
'Disable'
|
||||
);
|
||||
|
||||
-- Add the new columns
|
||||
ALTER TABLE local_site
|
||||
ADD COLUMN post_upvotes federation_mode_enum DEFAULT 'All'::federation_mode_enum NOT NULL,
|
||||
ADD COLUMN post_downvotes federation_mode_enum DEFAULT 'All'::federation_mode_enum NOT NULL,
|
||||
ADD COLUMN comment_upvotes federation_mode_enum DEFAULT 'All'::federation_mode_enum NOT NULL,
|
||||
ADD COLUMN comment_downvotes federation_mode_enum DEFAULT 'All'::federation_mode_enum NOT NULL;
|
||||
|
||||
-- Copy over the enable_downvotes into the post and comment downvote settings
|
||||
WITH subquery AS (
|
||||
SELECT
|
||||
enable_downvotes,
|
||||
CASE WHEN enable_downvotes = TRUE THEN
|
||||
'All'::federation_mode_enum
|
||||
ELSE
|
||||
'Disable'::federation_mode_enum
|
||||
END
|
||||
FROM
|
||||
local_site)
|
||||
UPDATE
|
||||
local_site
|
||||
SET
|
||||
post_downvotes = subquery.case,
|
||||
comment_downvotes = subquery.case
|
||||
FROM
|
||||
subquery;
|
||||
|
||||
-- Drop the enable_downvotes column
|
||||
ALTER TABLE local_site
|
||||
DROP COLUMN enable_downvotes;
|
||||
|
Loading…
Reference in a new issue