2023-11-26 23:21:04 +13:00
from datetime import datetime , timedelta
2024-01-01 11:38:24 +13:00
from time import sleep
2023-11-26 23:21:04 +13:00
2024-02-02 15:30:03 +13:00
from flask import request , flash , json , url_for , current_app , redirect , g
2023-11-03 21:59:48 +13:00
from flask_login import login_required , current_user
from flask_babel import _
2024-04-07 19:40:52 +12:00
from slugify import slugify
2024-03-27 20:20:08 +13:00
from sqlalchemy import text , desc , or_
2023-11-03 21:59:48 +13:00
2024-03-22 12:22:19 +13:00
from app import db , celery , cache
2023-12-29 17:32:35 +13:00
from app . activitypub . routes import process_inbox_request , process_delete_request
2024-05-04 21:16:55 +01:00
from app . activitypub . signature import post_request , default_context
from app . activitypub . util import instance_allowed , instance_blocked
2024-01-04 16:00:19 +13:00
from app . admin . forms import FederationForm , SiteMiscForm , SiteProfileForm , EditCommunityForm , EditUserForm , \
2024-02-28 05:04:07 +13:00
EditTopicForm , SendNewsletterForm , AddUserForm
2024-03-04 12:13:14 +13:00
from app . admin . util import unsubscribe_from_everything_then_delete , unsubscribe_from_community , send_newsletter , \
2024-04-08 19:48:25 +12:00
topics_for_form
2023-12-31 12:09:20 +13:00
from app . community . util import save_icon_file , save_banner_file
2024-03-27 20:20:08 +13:00
from app . constants import REPORT_STATE_NEW , REPORT_STATE_ESCALATED
2024-01-01 14:49:15 +13:00
from app . models import AllowedInstances , BannedInstances , ActivityPubLog , utcnow , Site , Community , CommunityMember , \
2024-05-08 21:07:22 +12:00
User , Instance , File , Report , Topic , UserRegistration , Role , Post , PostReply , Language
2024-01-12 12:34:08 +13:00
from app . utils import render_template , permission_required , set_setting , get_setting , gibberish , markdown_to_html , \
2024-04-08 19:48:25 +12:00
moderating_communities , joined_communities , finalize_user_setup , theme_list , blocked_phrases , blocked_referrers , \
2024-05-08 21:07:22 +12:00
topic_tree , languages_for_form
2023-11-03 21:59:48 +13:00
from app . admin import bp
@bp.route ( ' / ' , methods = [ ' GET ' , ' POST ' ] )
@login_required
@permission_required ( ' change instance settings ' )
def admin_home ( ) :
2024-01-12 12:34:08 +13:00
return render_template ( ' admin/home.html ' , title = _ ( ' Admin ' ) , moderating_communities = moderating_communities ( current_user . get_id ( ) ) ,
2024-02-02 15:30:03 +13:00
joined_communities = joined_communities ( current_user . get_id ( ) ) ,
site = g . site )
2023-12-17 00:12:49 +13:00
@bp.route ( ' /site ' , methods = [ ' GET ' , ' POST ' ] )
@login_required
@permission_required ( ' change instance settings ' )
def admin_site ( ) :
form = SiteProfileForm ( )
site = Site . query . get ( 1 )
if site is None :
site = Site ( )
if form . validate_on_submit ( ) :
site . name = form . name . data
2024-04-22 19:23:26 +02:00
site . description = form . description . data #tagline
site . about = form . about . data
2023-12-17 00:12:49 +13:00
site . sidebar = form . sidebar . data
site . legal_information = form . legal_information . data
site . updated = utcnow ( )
2024-04-16 12:10:24 +02:00
site . contact_email = form . contact_email . data
2023-12-17 00:12:49 +13:00
if site . id is None :
db . session . add ( site )
db . session . commit ( )
flash ( ' Settings saved. ' )
elif request . method == ' GET ' :
form . name . data = site . name
form . description . data = site . description
2024-04-22 19:23:26 +02:00
form . about . data = site . about
2023-12-17 00:12:49 +13:00
form . sidebar . data = site . sidebar
form . legal_information . data = site . legal_information
2024-04-22 19:23:26 +02:00
form . contact_email . data = site . contact_email
2024-01-12 12:34:08 +13:00
return render_template ( ' admin/site.html ' , title = _ ( ' Site profile ' ) , form = form ,
moderating_communities = moderating_communities ( current_user . get_id ( ) ) ,
2024-02-02 15:30:03 +13:00
joined_communities = joined_communities ( current_user . get_id ( ) ) ,
site = g . site
2024-01-12 12:34:08 +13:00
)
2023-12-17 00:12:49 +13:00
@bp.route ( ' /misc ' , methods = [ ' GET ' , ' POST ' ] )
@login_required
@permission_required ( ' change instance settings ' )
def admin_misc ( ) :
form = SiteMiscForm ( )
site = Site . query . get ( 1 )
if site is None :
site = Site ( )
2024-02-07 18:33:25 +13:00
form . default_theme . choices = theme_list ( )
2023-12-17 00:12:49 +13:00
if form . validate_on_submit ( ) :
site . enable_downvotes = form . enable_downvotes . data
site . allow_local_image_posts = form . allow_local_image_posts . data
site . remote_image_cache_days = form . remote_image_cache_days . data
site . enable_nsfw = form . enable_nsfw . data
site . enable_nsfl = form . enable_nsfl . data
site . community_creation_admin_only = form . community_creation_admin_only . data
site . reports_email_admins = form . reports_email_admins . data
site . registration_mode = form . registration_mode . data
site . application_question = form . application_question . data
2024-03-22 14:35:51 +13:00
site . auto_decline_referrers = form . auto_decline_referrers . data
2024-01-13 11:12:31 +13:00
site . log_activitypub_json = form . log_activitypub_json . data
2023-12-17 00:12:49 +13:00
site . updated = utcnow ( )
2024-02-07 17:31:12 +13:00
site . default_theme = form . default_theme . data
2023-12-17 00:12:49 +13:00
if site . id is None :
db . session . add ( site )
db . session . commit ( )
2024-03-22 14:35:51 +13:00
cache . delete_memoized ( blocked_referrers )
2023-12-17 00:12:49 +13:00
flash ( ' Settings saved. ' )
elif request . method == ' GET ' :
form . enable_downvotes . data = site . enable_downvotes
form . allow_local_image_posts . data = site . allow_local_image_posts
form . remote_image_cache_days . data = site . remote_image_cache_days
form . enable_nsfw . data = site . enable_nsfw
form . enable_nsfl . data = site . enable_nsfl
form . community_creation_admin_only . data = site . community_creation_admin_only
form . reports_email_admins . data = site . reports_email_admins
form . registration_mode . data = site . registration_mode
form . application_question . data = site . application_question
2024-03-22 14:35:51 +13:00
form . auto_decline_referrers . data = site . auto_decline_referrers
2024-01-13 11:12:31 +13:00
form . log_activitypub_json . data = site . log_activitypub_json
2024-02-07 17:31:12 +13:00
form . default_theme . data = site . default_theme if site . default_theme is not None else ' '
2024-01-12 12:34:08 +13:00
return render_template ( ' admin/misc.html ' , title = _ ( ' Misc settings ' ) , form = form ,
moderating_communities = moderating_communities ( current_user . get_id ( ) ) ,
2024-02-02 15:30:03 +13:00
joined_communities = joined_communities ( current_user . get_id ( ) ) ,
site = g . site
2024-01-12 12:34:08 +13:00
)
2023-12-17 00:12:49 +13:00
@bp.route ( ' /federation ' , methods = [ ' GET ' , ' POST ' ] )
@login_required
@permission_required ( ' change instance settings ' )
def admin_federation ( ) :
form = FederationForm ( )
site = Site . query . get ( 1 )
if site is None :
site = Site ( )
# todo: finish form
site . updated = utcnow ( )
2023-11-03 21:59:48 +13:00
if form . validate_on_submit ( ) :
if form . use_allowlist . data :
set_setting ( ' use_allowlist ' , True )
db . session . execute ( text ( ' DELETE FROM allowed_instances ' ) )
for allow in form . allowlist . data . split ( ' \n ' ) :
if allow . strip ( ) :
db . session . add ( AllowedInstances ( domain = allow . strip ( ) ) )
2024-03-22 12:22:19 +13:00
cache . delete_memoized ( instance_allowed , allow . strip ( ) )
2023-11-03 21:59:48 +13:00
if form . use_blocklist . data :
set_setting ( ' use_allowlist ' , False )
db . session . execute ( text ( ' DELETE FROM banned_instances ' ) )
for banned in form . blocklist . data . split ( ' \n ' ) :
if banned . strip ( ) :
db . session . add ( BannedInstances ( domain = banned . strip ( ) ) )
2024-03-22 12:22:19 +13:00
cache . delete_memoized ( instance_blocked , banned . strip ( ) )
site . blocked_phrases = form . blocked_phrases . data
2024-05-16 15:44:42 +12:00
set_setting ( ' actor_blocked_words ' , form . blocked_actors . data )
2024-03-22 12:22:19 +13:00
cache . delete_memoized ( blocked_phrases )
2024-05-16 15:44:42 +12:00
cache . delete_memoized ( get_setting , ' actor_blocked_words ' )
2023-11-03 21:59:48 +13:00
db . session . commit ( )
2024-03-22 12:22:19 +13:00
2023-11-03 21:59:48 +13:00
flash ( _ ( ' Admin settings saved ' ) )
elif request . method == ' GET ' :
form . use_allowlist . data = get_setting ( ' use_allowlist ' , False )
form . use_blocklist . data = not form . use_allowlist . data
instances = BannedInstances . query . all ( )
form . blocklist . data = ' \n ' . join ( [ instance . domain for instance in instances ] )
instances = AllowedInstances . query . all ( )
form . allowlist . data = ' \n ' . join ( [ instance . domain for instance in instances ] )
2024-03-22 12:22:19 +13:00
form . blocked_phrases . data = site . blocked_phrases
2024-05-16 15:44:42 +12:00
form . blocked_actors . data = get_setting ( ' actor_blocked_words ' , ' 88 ' )
2023-11-03 21:59:48 +13:00
2024-01-12 12:34:08 +13:00
return render_template ( ' admin/federation.html ' , title = _ ( ' Federation settings ' ) , form = form ,
moderating_communities = moderating_communities ( current_user . get_id ( ) ) ,
2024-02-02 15:30:03 +13:00
joined_communities = joined_communities ( current_user . get_id ( ) ) ,
site = g . site
2024-01-12 12:34:08 +13:00
)
2023-11-03 21:59:48 +13:00
2023-11-24 22:32:46 +13:00
@bp.route ( ' /activities ' , methods = [ ' GET ' ] )
@login_required
@permission_required ( ' change instance settings ' )
def admin_activities ( ) :
2023-11-26 23:21:04 +13:00
db . session . query ( ActivityPubLog ) . filter (
2023-12-17 00:12:49 +13:00
ActivityPubLog . created_at < utcnow ( ) - timedelta ( days = 3 ) ) . delete ( )
2023-11-26 23:21:04 +13:00
db . session . commit ( )
2023-12-23 11:32:22 +13:00
page = request . args . get ( ' page ' , 1 , type = int )
activities = ActivityPubLog . query . order_by ( desc ( ActivityPubLog . created_at ) ) . paginate ( page = page , per_page = 1000 , error_out = False )
2023-12-29 17:32:35 +13:00
next_url = url_for ( ' admin.admin_activities ' , page = activities . next_num ) if activities . has_next else None
prev_url = url_for ( ' admin.admin_activities ' , page = activities . prev_num ) if activities . has_prev and page != 1 else None
2023-12-23 11:32:22 +13:00
return render_template ( ' admin/activities.html ' , title = _ ( ' ActivityPub Log ' ) , next_url = next_url , prev_url = prev_url ,
2024-02-02 15:30:03 +13:00
activities = activities ,
site = g . site )
2023-12-23 11:32:22 +13:00
@bp.route ( ' /activity_json/<int:activity_id> ' )
@login_required
@permission_required ( ' change instance settings ' )
def activity_json ( activity_id ) :
activity = ActivityPubLog . query . get_or_404 ( activity_id )
return render_template ( ' admin/activity_json.html ' , title = _ ( ' Activity JSON ' ) ,
2024-02-02 15:30:03 +13:00
activity_json_data = json . loads ( activity . activity_json ) , activity = activity ,
current_app = current_app ,
site = g . site )
2023-12-26 12:36:20 +13:00
@bp.route ( ' /activity_json/<int:activity_id>/replay ' )
@login_required
@permission_required ( ' change instance settings ' )
def activity_replay ( activity_id ) :
activity = ActivityPubLog . query . get_or_404 ( activity_id )
2023-12-29 17:32:35 +13:00
request_json = json . loads ( activity . activity_json )
if ' type ' in request_json and request_json [ ' type ' ] == ' Delete ' and request_json [ ' id ' ] . endswith ( ' #delete ' ) :
2024-01-01 14:49:15 +13:00
process_delete_request ( request_json , activity . id , None )
2023-12-29 17:32:35 +13:00
else :
2024-01-01 14:49:15 +13:00
process_inbox_request ( request_json , activity . id , None )
2023-12-26 12:36:20 +13:00
return ' Ok '
2023-12-31 12:09:20 +13:00
@bp.route ( ' /communities ' , methods = [ ' GET ' ] )
@login_required
@permission_required ( ' administer all communities ' )
def admin_communities ( ) :
page = request . args . get ( ' page ' , 1 , type = int )
2024-01-07 13:45:02 +13:00
search = request . args . get ( ' search ' , ' ' )
2023-12-31 12:09:20 +13:00
2024-03-13 16:40:20 +13:00
communities = Community . query
2024-01-07 13:45:02 +13:00
if search :
communities = communities . filter ( Community . title . ilike ( f " % { search } % " ) )
communities = communities . order_by ( Community . title ) . paginate ( page = page , per_page = 1000 , error_out = False )
2023-12-31 12:09:20 +13:00
next_url = url_for ( ' admin.admin_communities ' , page = communities . next_num ) if communities . has_next else None
prev_url = url_for ( ' admin.admin_communities ' , page = communities . prev_num ) if communities . has_prev and page != 1 else None
return render_template ( ' admin/communities.html ' , title = _ ( ' Communities ' ) , next_url = next_url , prev_url = prev_url ,
2024-01-12 12:34:08 +13:00
communities = communities , moderating_communities = moderating_communities ( current_user . get_id ( ) ) ,
2024-02-02 15:30:03 +13:00
joined_communities = joined_communities ( current_user . get_id ( ) ) ,
site = g . site )
2023-12-31 12:09:20 +13:00
2024-05-19 11:41:02 +12:00
@bp.route ( ' /communities/no-topic ' , methods = [ ' GET ' ] )
@login_required
@permission_required ( ' administer all communities ' )
def admin_communities_no_topic ( ) :
page = request . args . get ( ' page ' , 1 , type = int )
search = request . args . get ( ' search ' , ' ' )
communities = Community . query . filter ( Community . topic_id == None )
if search :
communities = communities . filter ( Community . title . ilike ( f " % { search } % " ) )
2024-05-19 11:59:41 +12:00
communities = communities . order_by ( - Community . post_count ) . paginate ( page = page , per_page = 1000 , error_out = False )
2024-05-19 11:41:02 +12:00
next_url = url_for ( ' admin.admin_communities_no_topic ' , page = communities . next_num ) if communities . has_next else None
prev_url = url_for ( ' admin.admin_communities_no_topic ' , page = communities . prev_num ) if communities . has_prev and page != 1 else None
return render_template ( ' admin/communities.html ' , title = _ ( ' Communities with no topic ' ) , next_url = next_url , prev_url = prev_url ,
communities = communities , moderating_communities = moderating_communities ( current_user . get_id ( ) ) ,
joined_communities = joined_communities ( current_user . get_id ( ) ) ,
site = g . site )
2023-12-31 12:09:20 +13:00
@bp.route ( ' /community/<int:community_id>/edit ' , methods = [ ' GET ' , ' POST ' ] )
@login_required
@permission_required ( ' administer all communities ' )
def admin_community_edit ( community_id ) :
form = EditCommunityForm ( )
community = Community . query . get_or_404 ( community_id )
2024-03-04 12:13:14 +13:00
form . topic . choices = topics_for_form ( 0 )
2024-05-08 21:07:22 +12:00
form . languages . choices = languages_for_form ( )
2023-12-31 12:09:20 +13:00
if form . validate_on_submit ( ) :
community . name = form . url . data
community . title = form . title . data
community . description = form . description . data
2024-01-04 22:08:32 +13:00
community . description_html = markdown_to_html ( form . description . data )
2023-12-31 12:09:20 +13:00
community . rules = form . rules . data
2024-01-04 22:08:32 +13:00
community . rules_html = markdown_to_html ( form . rules . data )
2023-12-31 12:09:20 +13:00
community . nsfw = form . nsfw . data
2024-03-13 16:40:20 +13:00
community . banned = form . banned . data
2024-01-02 19:41:00 +13:00
community . local_only = form . local_only . data
community . restricted_to_mods = form . restricted_to_mods . data
community . new_mods_wanted = form . new_mods_wanted . data
2023-12-31 12:09:20 +13:00
community . show_home = form . show_home . data
community . show_popular = form . show_popular . data
community . show_all = form . show_all . data
community . low_quality = form . low_quality . data
community . content_retention = form . content_retention . data
2024-01-04 16:00:19 +13:00
community . topic_id = form . topic . data if form . topic . data != 0 else None
2024-01-21 15:44:13 +13:00
community . default_layout = form . default_layout . data
2024-04-18 20:51:08 +12:00
community . posting_warning = form . posting_warning . data
2024-05-08 21:07:22 +12:00
community . ignore_remote_language = form . ignore_remote_language . data
2024-03-04 12:13:14 +13:00
2023-12-31 12:09:20 +13:00
icon_file = request . files [ ' icon_file ' ]
if icon_file and icon_file . filename != ' ' :
if community . icon_id :
community . icon . delete_from_disk ( )
file = save_icon_file ( icon_file )
if file :
community . icon = file
banner_file = request . files [ ' banner_file ' ]
if banner_file and banner_file . filename != ' ' :
if community . image_id :
community . image . delete_from_disk ( )
file = save_banner_file ( banner_file )
if file :
community . image = file
2024-01-04 16:00:19 +13:00
2024-05-08 21:07:22 +12:00
# Languages of the community
db . session . execute ( text ( ' DELETE FROM " community_language " WHERE community_id = :community_id ' ) ,
{ ' community_id ' : community_id } )
for language_choice in form . languages . data :
community . languages . append ( Language . query . get ( language_choice ) )
# Always include the undetermined language, so posts with no language will be accepted
community . languages . append ( Language . query . filter ( Language . code == ' und ' ) . first ( ) )
2024-03-04 12:13:14 +13:00
db . session . commit ( )
2024-03-13 16:40:20 +13:00
if community . topic_id :
community . topic . num_communities = community . topic . communities . count ( )
2023-12-31 12:09:20 +13:00
db . session . commit ( )
flash ( _ ( ' Saved ' ) )
return redirect ( url_for ( ' admin.admin_communities ' ) )
else :
if not community . is_local ( ) :
flash ( _ ( ' This is a remote community - most settings here will be regularly overwritten with data from the original server. ' ) , ' warning ' )
form . url . data = community . name
form . title . data = community . title
form . description . data = community . description
form . rules . data = community . rules
form . nsfw . data = community . nsfw
2024-03-13 16:40:20 +13:00
form . banned . data = community . banned
2024-01-02 19:41:00 +13:00
form . local_only . data = community . local_only
form . new_mods_wanted . data = community . new_mods_wanted
form . restricted_to_mods . data = community . restricted_to_mods
2023-12-31 12:09:20 +13:00
form . show_home . data = community . show_home
form . show_popular . data = community . show_popular
form . show_all . data = community . show_all
form . low_quality . data = community . low_quality
form . content_retention . data = community . content_retention
2024-01-04 16:00:19 +13:00
form . topic . data = community . topic_id if community . topic_id else None
2024-01-21 15:44:13 +13:00
form . default_layout . data = community . default_layout
2024-04-18 20:51:08 +12:00
form . posting_warning . data = community . posting_warning
2024-05-08 21:07:22 +12:00
form . languages . data = community . language_ids ( )
form . ignore_remote_language . data = community . ignore_remote_language
2024-01-12 12:34:08 +13:00
return render_template ( ' admin/edit_community.html ' , title = _ ( ' Edit community ' ) , form = form , community = community ,
moderating_communities = moderating_communities ( current_user . get_id ( ) ) ,
2024-02-02 15:30:03 +13:00
joined_communities = joined_communities ( current_user . get_id ( ) ) ,
site = g . site
2024-01-12 12:34:08 +13:00
)
2023-12-31 12:09:20 +13:00
@bp.route ( ' /community/<int:community_id>/delete ' , methods = [ ' GET ' ] )
@login_required
@permission_required ( ' administer all communities ' )
def admin_community_delete ( community_id ) :
2024-01-01 11:38:24 +13:00
community = Community . query . get_or_404 ( community_id )
community . banned = True # Unsubscribing everyone could take a long time so until that is completed hide this community from the UI by banning it.
community . last_active = utcnow ( )
db . session . commit ( )
unsubscribe_everyone_then_delete ( community . id )
flash ( _ ( ' Community deleted ' ) )
return redirect ( url_for ( ' admin.admin_communities ' ) )
def unsubscribe_everyone_then_delete ( community_id ) :
if current_app . debug :
unsubscribe_everyone_then_delete_task ( community_id )
else :
unsubscribe_everyone_then_delete_task . delay ( community_id )
@celery.task
def unsubscribe_everyone_then_delete_task ( community_id ) :
community = Community . query . get_or_404 ( community_id )
if not community . is_local ( ) :
members = CommunityMember . query . filter_by ( community_id = community_id ) . all ( )
for member in members :
user = User . query . get ( member . user_id )
2024-01-01 14:49:15 +13:00
unsubscribe_from_community ( community , user )
else :
# todo: federate delete of local community out to all following instances
. . .
2024-01-01 11:38:24 +13:00
sleep ( 5 )
community . delete_dependencies ( )
db . session . delete ( community ) # todo: when a remote community is deleted it will be able to be re-created by using the 'Add remote' function. Not ideal. Consider soft-delete.
2024-01-07 09:29:36 +13:00
db . session . commit ( )
2024-01-01 14:49:15 +13:00
2024-01-04 16:00:19 +13:00
@bp.route ( ' /topics ' , methods = [ ' GET ' ] )
@login_required
@permission_required ( ' administer all communities ' )
def admin_topics ( ) :
2024-03-04 12:13:14 +13:00
topics = topic_tree ( )
2024-01-12 12:34:08 +13:00
return render_template ( ' admin/topics.html ' , title = _ ( ' Topics ' ) , topics = topics ,
moderating_communities = moderating_communities ( current_user . get_id ( ) ) ,
2024-02-02 15:30:03 +13:00
joined_communities = joined_communities ( current_user . get_id ( ) ) ,
site = g . site
2024-01-12 12:34:08 +13:00
)
2024-01-04 16:00:19 +13:00
@bp.route ( ' /topic/add ' , methods = [ ' GET ' , ' POST ' ] )
@login_required
@permission_required ( ' administer all communities ' )
def admin_topic_add ( ) :
form = EditTopicForm ( )
2024-03-04 12:13:14 +13:00
form . parent_id . choices = topics_for_form ( 0 )
2024-01-04 16:00:19 +13:00
if form . validate_on_submit ( ) :
2024-04-07 19:40:52 +12:00
topic = Topic ( name = form . name . data , machine_name = slugify ( form . machine_name . data . strip ( ) ) , num_communities = 0 )
2024-03-04 12:13:14 +13:00
if form . parent_id . data :
topic . parent_id = form . parent_id . data
else :
topic . parent_id = None
2024-01-04 16:00:19 +13:00
db . session . add ( topic )
db . session . commit ( )
2024-03-04 12:13:14 +13:00
2024-01-04 16:00:19 +13:00
flash ( _ ( ' Saved ' ) )
return redirect ( url_for ( ' admin.admin_topics ' ) )
2024-01-12 12:34:08 +13:00
return render_template ( ' admin/edit_topic.html ' , title = _ ( ' Add topic ' ) , form = form ,
moderating_communities = moderating_communities ( current_user . get_id ( ) ) ,
2024-02-02 15:30:03 +13:00
joined_communities = joined_communities ( current_user . get_id ( ) ) ,
site = g . site
2024-01-12 12:34:08 +13:00
)
2024-01-04 16:00:19 +13:00
@bp.route ( ' /topic/<int:topic_id>/edit ' , methods = [ ' GET ' , ' POST ' ] )
@login_required
@permission_required ( ' administer all communities ' )
def admin_topic_edit ( topic_id ) :
form = EditTopicForm ( )
topic = Topic . query . get_or_404 ( topic_id )
2024-03-04 12:13:14 +13:00
form . parent_id . choices = topics_for_form ( topic_id )
2024-01-04 16:00:19 +13:00
if form . validate_on_submit ( ) :
topic . name = form . name . data
2024-03-04 12:13:14 +13:00
topic . num_communities = topic . communities . count ( )
2024-01-29 22:18:06 +13:00
topic . machine_name = form . machine_name . data
2024-03-04 12:13:14 +13:00
if form . parent_id . data :
topic . parent_id = form . parent_id . data
else :
topic . parent_id = None
2024-01-04 16:00:19 +13:00
db . session . commit ( )
flash ( _ ( ' Saved ' ) )
return redirect ( url_for ( ' admin.admin_topics ' ) )
else :
form . name . data = topic . name
2024-01-29 22:18:06 +13:00
form . machine_name . data = topic . machine_name
2024-03-04 12:13:14 +13:00
form . parent_id . data = topic . parent_id
2024-01-12 12:34:08 +13:00
return render_template ( ' admin/edit_topic.html ' , title = _ ( ' Edit topic ' ) , form = form , topic = topic ,
moderating_communities = moderating_communities ( current_user . get_id ( ) ) ,
2024-02-02 15:30:03 +13:00
joined_communities = joined_communities ( current_user . get_id ( ) ) ,
site = g . site
2024-01-12 12:34:08 +13:00
)
2024-01-04 16:00:19 +13:00
@bp.route ( ' /topic/<int:topic_id>/delete ' , methods = [ ' GET ' ] )
@login_required
@permission_required ( ' administer all communities ' )
def admin_topic_delete ( topic_id ) :
topic = Topic . query . get_or_404 ( topic_id )
topic . num_communities = topic . communities . count ( )
if topic . num_communities == 0 :
db . session . delete ( topic )
flash ( _ ( ' Topic deleted ' ) )
else :
flash ( _ ( ' Cannot delete topic with communities assigned to it. ' , ' error ' ) )
db . session . commit ( )
return redirect ( url_for ( ' admin.admin_topics ' ) )
2024-01-01 14:49:15 +13:00
@bp.route ( ' /users ' , methods = [ ' GET ' ] )
@login_required
@permission_required ( ' administer all users ' )
def admin_users ( ) :
page = request . args . get ( ' page ' , 1 , type = int )
2024-01-02 16:07:41 +13:00
search = request . args . get ( ' search ' , ' ' )
local_remote = request . args . get ( ' local_remote ' , ' ' )
2024-01-01 14:49:15 +13:00
2024-01-02 16:07:41 +13:00
users = User . query . filter_by ( deleted = False )
if local_remote == ' local ' :
users = users . filter_by ( ap_id = None )
if local_remote == ' remote ' :
users = users . filter ( User . ap_id != None )
2024-01-07 14:45:16 +13:00
if search :
users = users . filter ( User . email . ilike ( f " % { search } % " ) )
2024-01-02 16:07:41 +13:00
users = users . order_by ( User . user_name ) . paginate ( page = page , per_page = 1000 , error_out = False )
2024-01-01 14:49:15 +13:00
next_url = url_for ( ' admin.admin_users ' , page = users . next_num ) if users . has_next else None
prev_url = url_for ( ' admin.admin_users ' , page = users . prev_num ) if users . has_prev and page != 1 else None
2024-01-02 16:07:41 +13:00
return render_template ( ' admin/users.html ' , title = _ ( ' Users ' ) , next_url = next_url , prev_url = prev_url , users = users ,
2024-01-12 12:34:08 +13:00
local_remote = local_remote , search = search ,
moderating_communities = moderating_communities ( current_user . get_id ( ) ) ,
2024-02-27 06:03:08 +13:00
joined_communities = joined_communities ( current_user . get_id ( ) ) ,
site = g . site
)
@bp.route ( ' /users/trash ' , methods = [ ' GET ' ] )
@login_required
@permission_required ( ' administer all users ' )
def admin_users_trash ( ) :
page = request . args . get ( ' page ' , 1 , type = int )
search = request . args . get ( ' search ' , ' ' )
local_remote = request . args . get ( ' local_remote ' , ' ' )
2024-04-12 09:59:06 +12:00
type = request . args . get ( ' type ' , ' bad_rep ' )
2024-02-27 06:03:08 +13:00
users = User . query . filter_by ( deleted = False )
if local_remote == ' local ' :
users = users . filter_by ( ap_id = None )
if local_remote == ' remote ' :
users = users . filter ( User . ap_id != None )
if search :
users = users . filter ( User . email . ilike ( f " % { search } % " ) )
2024-04-12 09:45:14 +12:00
2024-04-12 09:59:06 +12:00
if type == ' ' or type == ' bad_rep ' :
users = users . filter ( User . reputation < - 10 )
users = users . order_by ( User . reputation ) . paginate ( page = page , per_page = 1000 , error_out = False )
elif type == ' bad_attitude ' :
users = users . filter ( User . attitude < 0.0 )
users = users . order_by ( - User . attitude ) . paginate ( page = page , per_page = 1000 , error_out = False )
2024-02-27 06:03:08 +13:00
next_url = url_for ( ' admin.admin_users_trash ' , page = users . next_num ) if users . has_next else None
prev_url = url_for ( ' admin.admin_users_trash ' , page = users . prev_num ) if users . has_prev and page != 1 else None
return render_template ( ' admin/users.html ' , title = _ ( ' Problematic users ' ) , next_url = next_url , prev_url = prev_url , users = users ,
2024-04-12 09:59:06 +12:00
local_remote = local_remote , search = search , type = type ,
2024-02-27 06:03:08 +13:00
moderating_communities = moderating_communities ( current_user . get_id ( ) ) ,
2024-02-02 15:30:03 +13:00
joined_communities = joined_communities ( current_user . get_id ( ) ) ,
site = g . site
2024-01-12 12:34:08 +13:00
)
2024-01-01 14:49:15 +13:00
2024-02-29 18:20:12 +13:00
@bp.route ( ' /content/trash ' , methods = [ ' GET ' ] )
@login_required
@permission_required ( ' administer all users ' )
def admin_content_trash ( ) :
page = request . args . get ( ' page ' , 1 , type = int )
posts = Post . query . filter ( Post . posted_at > utcnow ( ) - timedelta ( days = 3 ) ) . order_by ( Post . score )
posts = posts . paginate ( page = page , per_page = 100 , error_out = False )
next_url = url_for ( ' admin.admin_content_trash ' , page = posts . next_num ) if posts . has_next else None
prev_url = url_for ( ' admin.admin_content_trash ' , page = posts . prev_num ) if posts . has_prev and page != 1 else None
return render_template ( ' admin/posts.html ' , title = _ ( ' Bad posts ' ) , next_url = next_url , prev_url = prev_url , posts = posts ,
moderating_communities = moderating_communities ( current_user . get_id ( ) ) ,
joined_communities = joined_communities ( current_user . get_id ( ) ) ,
site = g . site
)
2024-05-07 19:44:39 +12:00
@bp.route ( ' /content/spam ' , methods = [ ' GET ' ] )
@login_required
@permission_required ( ' administer all users ' )
def admin_content_spam ( ) :
# Essentially the same as admin_content_trash() except only shows heavily downvoted posts by new users - who are usually spammers
page = request . args . get ( ' page ' , 1 , type = int )
replies_page = request . args . get ( ' replies_page ' , 1 , type = int )
posts = Post . query . join ( User , User . id == Post . user_id ) . \
filter ( User . created > utcnow ( ) - timedelta ( days = 3 ) ) . \
filter ( Post . posted_at > utcnow ( ) - timedelta ( days = 3 ) ) . \
filter ( Post . score < = 0 ) . order_by ( Post . score )
posts = posts . paginate ( page = page , per_page = 100 , error_out = False )
post_replies = PostReply . query . join ( User , User . id == PostReply . user_id ) . \
filter ( User . created > utcnow ( ) - timedelta ( days = 3 ) ) . \
filter ( PostReply . posted_at > utcnow ( ) - timedelta ( days = 3 ) ) . \
filter ( PostReply . score < = 0 ) . order_by ( PostReply . score )
post_replies = post_replies . paginate ( page = replies_page , per_page = 100 , error_out = False )
next_url = url_for ( ' admin.admin_content_spam ' , page = posts . next_num ) if posts . has_next else None
prev_url = url_for ( ' admin.admin_content_spam ' , page = posts . prev_num ) if posts . has_prev and page != 1 else None
next_url_replies = url_for ( ' admin.admin_content_spam ' , replies_page = post_replies . next_num ) if post_replies . has_next else None
prev_url_replies = url_for ( ' admin.admin_content_spam ' , replies_page = post_replies . prev_num ) if post_replies . has_prev and replies_page != 1 else None
return render_template ( ' admin/spam_posts.html ' , title = _ ( ' Likely spam ' ) ,
next_url = next_url , prev_url = prev_url ,
next_url_replies = next_url_replies , prev_url_replies = prev_url_replies ,
posts = posts , post_replies = post_replies ,
moderating_communities = moderating_communities ( current_user . get_id ( ) ) ,
joined_communities = joined_communities ( current_user . get_id ( ) ) ,
site = g . site
)
2024-02-02 15:30:03 +13:00
@bp.route ( ' /approve_registrations ' , methods = [ ' GET ' ] )
@login_required
@permission_required ( ' approve registrations ' )
def admin_approve_registrations ( ) :
registrations = UserRegistration . query . filter_by ( status = 0 ) . order_by ( UserRegistration . created_at ) . all ( )
recently_approved = UserRegistration . query . filter_by ( status = 1 ) . order_by ( desc ( UserRegistration . approved_at ) ) . limit ( 30 )
return render_template ( ' admin/approve_registrations.html ' ,
registrations = registrations ,
recently_approved = recently_approved ,
site = g . site )
@bp.route ( ' /approve_registrations/<int:user_id>/approve ' , methods = [ ' GET ' ] )
@login_required
@permission_required ( ' approve registrations ' )
def admin_approve_registrations_approve ( user_id ) :
user = User . query . get_or_404 ( user_id )
registration = UserRegistration . query . filter_by ( status = 0 , user_id = user_id ) . first ( )
if registration :
registration . status = 1
registration . approved_at = utcnow ( )
registration . approved_by = current_user . id
db . session . commit ( )
if user . verified :
finalize_user_setup ( user , True )
flash ( _ ( ' Registration approved. ' ) )
return redirect ( url_for ( ' admin.admin_approve_registrations ' ) )
2024-01-01 14:49:15 +13:00
@bp.route ( ' /user/<int:user_id>/edit ' , methods = [ ' GET ' , ' POST ' ] )
@login_required
@permission_required ( ' administer all users ' )
def admin_user_edit ( user_id ) :
form = EditUserForm ( )
user = User . query . get_or_404 ( user_id )
if form . validate_on_submit ( ) :
user . bot = form . bot . data
2024-02-19 15:01:53 +13:00
user . verified = form . verified . data
user . banned = form . banned . data
2024-03-23 07:08:28 +13:00
if form . remove_avatar . data and user . avatar_id :
file = File . query . get ( user . avatar_id )
file . delete_from_disk ( )
user . avatar_id = None
db . session . delete ( file )
if form . remove_banner . data and user . cover_id :
file = File . query . get ( user . cover_id )
file . delete_from_disk ( )
user . cover_id = None
db . session . delete ( file )
2024-02-28 10:27:31 +13:00
# Update user roles. The UI only lets the user choose 1 role but the DB structure allows for multiple roles per user.
2024-03-04 12:21:06 +13:00
db . session . execute ( text ( ' DELETE FROM user_role WHERE user_id = :user_id ' ) , { ' user_id ' : user . id } )
2024-02-28 10:27:31 +13:00
user . roles . append ( Role . query . get ( form . role . data ) )
2024-02-28 10:38:44 +13:00
if form . role . data == 4 :
flash ( _ ( " Permissions are cached for 50 seconds so new admin roles won ' t take effect immediately. " ) )
2024-02-28 10:27:31 +13:00
2024-01-01 14:49:15 +13:00
db . session . commit ( )
2024-04-20 16:26:33 +12:00
2024-01-01 14:49:15 +13:00
flash ( _ ( ' Saved ' ) )
2024-03-04 12:21:06 +13:00
return redirect ( url_for ( ' admin.admin_users ' , local_remote = ' local ' if user . is_local ( ) else ' remote ' ) )
2024-01-01 14:49:15 +13:00
else :
if not user . is_local ( ) :
flash ( _ ( ' This is a remote user - most settings here will be regularly overwritten with data from the original server. ' ) , ' warning ' )
form . bot . data = user . bot
2024-02-19 15:01:53 +13:00
form . verified . data = user . verified
form . banned . data = user . banned
2024-03-04 12:21:06 +13:00
if user . roles and user . roles . count ( ) > 0 :
2024-02-28 10:27:31 +13:00
form . role . data = user . roles [ 0 ] . id
2024-01-01 14:49:15 +13:00
2024-01-12 12:34:08 +13:00
return render_template ( ' admin/edit_user.html ' , title = _ ( ' Edit user ' ) , form = form , user = user ,
moderating_communities = moderating_communities ( current_user . get_id ( ) ) ,
2024-02-02 15:30:03 +13:00
joined_communities = joined_communities ( current_user . get_id ( ) ) ,
site = g . site
2024-01-12 12:34:08 +13:00
)
2024-01-01 14:49:15 +13:00
2024-02-28 05:04:07 +13:00
@bp.route ( ' /users/add ' , methods = [ ' GET ' , ' POST ' ] )
@login_required
@permission_required ( ' administer all users ' )
def admin_users_add ( ) :
form = AddUserForm ( )
user = User ( )
if form . validate_on_submit ( ) :
user . user_name = form . user_name . data
user . set_password ( form . password . data )
user . about = form . about . data
user . email = form . email . data
user . about_html = markdown_to_html ( form . about . data )
user . matrix_user_id = form . matrix_user_id . data
user . bot = form . bot . data
profile_file = request . files [ ' profile_file ' ]
if profile_file and profile_file . filename != ' ' :
# remove old avatar
if user . avatar_id :
file = File . query . get ( user . avatar_id )
file . delete_from_disk ( )
user . avatar_id = None
db . session . delete ( file )
# add new avatar
file = save_icon_file ( profile_file , ' users ' )
if file :
user . avatar = file
banner_file = request . files [ ' banner_file ' ]
if banner_file and banner_file . filename != ' ' :
# remove old cover
if user . cover_id :
file = File . query . get ( user . cover_id )
file . delete_from_disk ( )
user . cover_id = None
db . session . delete ( file )
# add new cover
file = save_banner_file ( banner_file , ' users ' )
if file :
user . cover = file
user . newsletter = form . newsletter . data
user . ignore_bots = form . ignore_bots . data
user . show_nsfw = form . nsfw . data
user . show_nsfl = form . nsfl . data
from app . activitypub . signature import RsaKeys
user . verified = True
2024-05-11 13:22:23 +01:00
user . instance_id = 1
2024-02-28 05:04:07 +13:00
user . last_seen = utcnow ( )
private_key , public_key = RsaKeys . generate_keypair ( )
user . private_key = private_key
user . public_key = public_key
2024-03-24 01:53:18 +00:00
user . ap_profile_id = f " https:// { current_app . config [ ' SERVER_NAME ' ] } /u/ { user . user_name } " . lower ( )
2024-02-28 05:04:07 +13:00
user . ap_public_url = f " https:// { current_app . config [ ' SERVER_NAME ' ] } /u/ { user . user_name } "
user . ap_inbox_url = f " https:// { current_app . config [ ' SERVER_NAME ' ] } /u/ { user . user_name } /inbox "
2024-02-28 10:27:31 +13:00
user . roles . append ( Role . query . get ( form . role . data ) )
2024-02-28 05:04:07 +13:00
db . session . add ( user )
db . session . commit ( )
flash ( _ ( ' User added ' ) )
return redirect ( url_for ( ' admin.admin_users ' , local_remote = ' local ' ) )
return render_template ( ' admin/add_user.html ' , title = _ ( ' Add user ' ) , form = form , user = user ,
moderating_communities = moderating_communities ( current_user . get_id ( ) ) ,
joined_communities = joined_communities ( current_user . get_id ( ) ) ,
site = g . site
)
2024-01-01 14:49:15 +13:00
@bp.route ( ' /user/<int:user_id>/delete ' , methods = [ ' GET ' ] )
@login_required
@permission_required ( ' administer all users ' )
def admin_user_delete ( user_id ) :
user = User . query . get_or_404 ( user_id )
user . banned = True # Unsubscribing everyone could take a long time so until that is completed hide this user from the UI by banning it.
user . last_active = utcnow ( )
db . session . commit ( )
if user . is_local ( ) :
unsubscribe_from_everything_then_delete ( user . id )
else :
user . deleted = True
user . delete_dependencies ( )
db . session . commit ( )
flash ( _ ( ' User deleted ' ) )
return redirect ( url_for ( ' admin.admin_users ' ) )
2024-01-02 16:07:41 +13:00
@bp.route ( ' /reports ' , methods = [ ' GET ' ] )
@login_required
@permission_required ( ' administer all users ' )
def admin_reports ( ) :
2024-01-01 14:49:15 +13:00
2024-01-02 16:07:41 +13:00
page = request . args . get ( ' page ' , 1 , type = int )
search = request . args . get ( ' search ' , ' ' )
local_remote = request . args . get ( ' local_remote ' , ' ' )
2024-01-01 14:49:15 +13:00
2024-03-27 20:20:08 +13:00
reports = Report . query . filter ( or_ ( Report . status == REPORT_STATE_NEW , Report . status == REPORT_STATE_ESCALATED ) )
2024-01-02 16:07:41 +13:00
if local_remote == ' local ' :
reports = reports . filter_by ( ap_id = None )
if local_remote == ' remote ' :
reports = reports . filter ( Report . ap_id != None )
reports = reports . order_by ( desc ( Report . created_at ) ) . paginate ( page = page , per_page = 1000 , error_out = False )
2024-01-01 14:49:15 +13:00
2024-01-02 16:07:41 +13:00
next_url = url_for ( ' admin.admin_reports ' , page = reports . next_num ) if reports . has_next else None
prev_url = url_for ( ' admin.admin_reports ' , page = reports . prev_num ) if reports . has_prev and page != 1 else None
2024-01-01 14:49:15 +13:00
2024-01-02 16:07:41 +13:00
return render_template ( ' admin/reports.html ' , title = _ ( ' Reports ' ) , next_url = next_url , prev_url = prev_url , reports = reports ,
2024-01-12 12:34:08 +13:00
local_remote = local_remote , search = search ,
moderating_communities = moderating_communities ( current_user . get_id ( ) ) ,
2024-02-02 15:30:03 +13:00
joined_communities = joined_communities ( current_user . get_id ( ) ) ,
site = g . site
2024-02-24 20:01:38 +13:00
)
@bp.route ( ' /newsletter ' , methods = [ ' GET ' , ' POST ' ] )
@login_required
@permission_required ( ' administer all users ' )
def newsletter ( ) :
form = SendNewsletterForm ( )
if form . validate_on_submit ( ) :
send_newsletter ( form )
flash ( ' Newsletter sent ' )
return redirect ( url_for ( ' admin.newsletter ' ) )
return render_template ( " admin/newsletter.html " , form = form , title = _ ( ' Send newsletter ' ) ,
moderating_communities = moderating_communities ( current_user . get_id ( ) ) ,
joined_communities = joined_communities ( current_user . get_id ( ) ) ,
site = g . site
)