from flask import Blueprint, render_template, request, redirect, url_for, flash, abort, jsonify, make_response from app.services import groups, economy from app.models.user import User from app.models.groups import Group, GroupMember, GroupJoinRequest, GroupRole, GroupRolePermission, GroupWallPost, GroupEconomy, GroupIcon, GroupSettings, GroupStatus from app.util import auth, websiteFeatures, textfilter, s3helper, membership, transactions from app.extensions import db, limiter, csrf from slugify import slugify from functools import wraps from app.util.assetvalidation import ValidateClothingImage from app.enums.MembershipType import MembershipType from app.enums.TransactionType import TransactionType import hashlib import logging from io import BytesIO import string allowedCharacters = string.ascii_letters + string.digits + string.punctuation + " " + "™®" groups_page = Blueprint('groups_page', __name__, template_folder='templates') def CountAlnumericCharacters( string : str ): count = 0 for char in string: if char.isalnum(): count += 1 return count def GetUserGroups( user : User ) -> list[GroupMember]: return GroupMember.query.filter_by(user_id=user.id).all() def AdminPermissionRequired( GroupObj : Group | int, TargetUser : User | int ): GroupObj : Group = groups.GetGroupFromId(GroupObj) TargetUser : User = groups.GetUserFromId(TargetUser) if GroupObj is None or TargetUser is None: return abort(403) UserCurrentRole : GroupRole = groups.GetUserRolesetInGroup(TargetUser, GroupObj) UserPermissions : GroupRolePermission = UserCurrentRole.permissions isGroupAdmin = UserPermissions.change_rank or \ UserPermissions.manage_clan or \ UserPermissions.manage_relationships or \ UserPermissions.remove_members or \ UserPermissions.spend_group_funds if not isGroupAdmin: return abort(403) @groups_page.route('/groups/admin/', methods=['GET']) @auth.authenticated_required def groupadmin(groupid): try: groupObject : Group = groups.GetGroupFromId(groupid) except groups.GroupExceptions.GroupDoesNotExist: return abort(404) AuthenticatedUser : User = auth.GetCurrentUser() if groupObject is None: return abort(404) AdminPermissionRequired(groupObject, AuthenticatedUser) return redirect(f"/groups/admin/{groupid}/info") @groups_page.route("/groups/admin//kick/", methods=['DELETE']) @auth.authenticated_required @limiter.limit("25/minute") def groupadmin_kickmember(groupid, userid): try: groupObject : Group = groups.GetGroupFromId(groupid) except groups.GroupExceptions.GroupDoesNotExist: return abort(404) AuthenticatedUser : User = auth.GetCurrentUser() AdminPermissionRequired(groupObject, AuthenticatedUser) UserCurrentRole : GroupRole = groups.GetUserRolesetInGroup(AuthenticatedUser, groupObject) UserPermissions : GroupRolePermission = UserCurrentRole.permissions if not UserPermissions.remove_members: flash("You do not have permission to kick a user.", "error") return jsonify({"success": False}),403 try: TargetUser : User = groups.GetUserFromId(userid) except groups.GroupExceptions.UserDoesNotExist: flash("The user you are trying to kick does not exist.", "error") return jsonify({"success": False}),404 if TargetUser == AuthenticatedUser: flash("You cannot kick yourself.", "error") return jsonify({"success": False}),403 TargetUserCurrentRole : GroupRole = groups.GetUserRolesetInGroup(TargetUser, groupObject) if TargetUserCurrentRole.rank == 0: flash("This user is not a member of this group.", "error") return jsonify({"success": False}),400 if TargetUserCurrentRole.rank >= UserCurrentRole.rank: flash("You cannot kick a user with the same or higher rank than you.", "error") return jsonify({"success": False}),403 groups.RemoveUserFromGroup(TargetUser, groupObject) flash(f"You have kicked {TargetUser.username} from this group.", "success") return jsonify({"success": True}),200 @groups_page.route("/groups/admin//members", methods=['GET']) @auth.authenticated_required def groupadmin_members(groupid): try: groupObject : Group = groups.GetGroupFromId(groupid) except groups.GroupExceptions.GroupDoesNotExist: return abort(404) AuthenticatedUser : User = auth.GetCurrentUser() if groupObject is None: return abort(404) AdminPermissionRequired(groupObject, AuthenticatedUser) UserCurrentRole : GroupRole = groups.GetUserRolesetInGroup(AuthenticatedUser, groupObject) UserPermissions : GroupRolePermission = UserCurrentRole.permissions searchByUsername = request.args.get("search", None, str) searchByRole = request.args.get("role", None, int) # Role Id pageNumber = request.args.get("page", 1, int) if searchByUsername is not None: if CountAlnumericCharacters(searchByUsername) < 3: searchByUsername = None if searchByRole is not None: try: searchByRole : GroupRole = groups.GetRolesetFromId(searchByRole) if searchByRole.group_id != groupObject.id: searchByRole = None except groups.GroupExceptions.RolesetDoesNotExist: searchByRole = None if pageNumber < 1: pageNumber = 1 if searchByUsername is None and searchByRole is None: Members : list[GroupMember] = GroupMember.query.filter_by(group_id=groupObject.id) elif searchByUsername is None and searchByRole is not None: Members : list[GroupMember] = GroupMember.query.filter_by(group_id=groupObject.id, group_role_id=searchByRole.id) elif searchByUsername is not None and searchByRole is None: Members : list[GroupMember] = GroupMember.query.filter_by(group_id=groupObject.id).join(User).filter(User.username.ilike(f"%{searchByUsername}%")) else: Members : list[GroupMember] = GroupMember.query.filter_by(group_id=groupObject.id, group_role_id=searchByRole.id).join(User).filter(User.username.ilike(f"%{searchByUsername}%")) Members = Members.order_by(GroupMember.created_at.desc()).paginate(page = pageNumber, per_page = 12, error_out = False) SortedRoles = groupObject.roles SortedRoles.sort(key=lambda x: x.rank, reverse=False) JoinRequestCount = GroupJoinRequest.query.filter_by(group_id=groupObject.id).count() return render_template( "groups/admin_subpage/members.html", group=groupObject, user=AuthenticatedUser, role=UserCurrentRole, permissions=UserPermissions, page="members", searchByUsername=searchByUsername, searchByRole=searchByRole, pageNumber=pageNumber, Members=Members, SortedRoles = SortedRoles, JoinRequestCount=JoinRequestCount ) @groups_page.route("/groups/admin//change_role//", methods=['POST']) @auth.authenticated_required @limiter.limit("15/minute") def groupadmin_members_change_role(groupid, userid, roleid): try: groupObject : Group = groups.GetGroupFromId(groupid) except groups.GroupExceptions.GroupDoesNotExist: return abort(404) AuthenticatedUser : User = auth.GetCurrentUser() if groupObject is None: return abort(404) AdminPermissionRequired(groupObject, AuthenticatedUser) UserCurrentRole : GroupRole = groups.GetUserRolesetInGroup(AuthenticatedUser, groupObject) UserPermissions : GroupRolePermission = UserCurrentRole.permissions if not UserPermissions.change_rank: flash("You do not have permission to change a user's role.", "error") return jsonify({"success": False}),403 if userid == AuthenticatedUser.id: flash("You cannot change your own role.", "error") return jsonify({"success": False}),400 try: TargetRole : GroupRole = groups.GetRolesetFromId(roleid) if TargetRole.group_id != groupObject.id: TargetRole = None except groups.GroupExceptions.RolesetDoesNotExist: TargetRole = None if TargetRole is None or TargetRole.rank == 0: flash("The role you are trying to set is invalid.", "error") return jsonify({"success": False}),400 if TargetRole.rank > UserCurrentRole.rank: flash("You cannot set a user's role to a role higher than your own.", "error") return jsonify({"success": False}),400 try: TargetUser : User = groups.GetUserFromId(userid) except groups.GroupExceptions.UserDoesNotExist: flash("The user you are trying to set a role for does not exist.", "error") return jsonify({"success": False}),400 TargetUserCurrentRole : GroupRole = groups.GetUserRolesetInGroup(TargetUser, groupObject) if TargetUserCurrentRole.rank > UserCurrentRole.rank: flash("You cannot set a higher ranking user's role.", "error") return jsonify({"success": False}),400 if TargetUserCurrentRole.rank == 0: # Not in group flash("The user you are trying to set a role for is not in the group.", "error") return jsonify({"success": False}),400 if TargetUserCurrentRole == TargetRole: flash("The user you are trying to set a role for already has that role.", "error") return jsonify({"success": True}),200 groups.ChangeUserRole(TargetUser, TargetRole) flash(f"Successfully changed {TargetUser.username}'s role to {TargetRole.name}.", "success") return jsonify({"success": True}),200 @groups_page.route("/groups/admin//members", methods=['POST']) @auth.authenticated_required @csrf.exempt def groupadmin_members_search(groupid): try: groupObject : Group = groups.GetGroupFromId(groupid) except groups.GroupExceptions.GroupDoesNotExist: return abort(404) AuthenticatedUser : User = auth.GetCurrentUser() if groupObject is None: return abort(404) AdminPermissionRequired(groupObject, AuthenticatedUser) searchInput = request.form.get("search-input", "", str) searchByRole = request.form.get("search-by", 0, int) if searchByRole == 0: searchByRole = None if searchInput == "": searchInput = None if searchInput is None and searchByRole is None: return redirect(f"/groups/admin/{groupid}/members") elif searchInput is None and searchByRole is not None: return redirect(f"/groups/admin/{groupid}/members?role={searchByRole}") elif searchInput is not None and searchByRole is None: return redirect(f"/groups/admin/{groupid}/members?search={searchInput}") else: return redirect(f"/groups/admin/{groupid}/members?search={searchInput}&role={searchByRole}") @groups_page.route("/groups/admin//members/requests", methods=['GET']) @auth.authenticated_required def groupadmin_members_requests(groupid): try: groupObject : Group = groups.GetGroupFromId(groupid) except groups.GroupExceptions.GroupDoesNotExist: return abort(404) AuthenticatedUser : User = auth.GetCurrentUser() UserCurrentRole : GroupRole = groups.GetUserRolesetInGroup(AuthenticatedUser, groupObject) UserPermissions : GroupRolePermission = UserCurrentRole.permissions if not groupObject.settings.approval_required or not UserPermissions.invite_members: return abort(404) PageNumber = request.args.get( key = "page", default=1, type=int) JoinRequests : list[GroupJoinRequest] = GroupJoinRequest.query.filter_by(group_id=groupObject.id).order_by(GroupJoinRequest.created_at.desc()).paginate( page=PageNumber, per_page=12, error_out=False) return render_template("groups/admin_subpage/join_requests.html", group=groupObject, user=AuthenticatedUser, role=UserCurrentRole, permissions=UserPermissions, page="members", JoinRequests=JoinRequests) @groups_page.route("/groups/admin//members/requests/deny/", methods=['GET']) @auth.authenticated_required def groupadmin_members_requests_deny(groupid, userid): try: groupObject : Group = groups.GetGroupFromId(groupid) except groups.GroupExceptions.GroupDoesNotExist: return abort(404) AuthenticatedUser : User = auth.GetCurrentUser() UserCurrentRole : GroupRole = groups.GetUserRolesetInGroup(AuthenticatedUser, groupObject) UserPermissions : GroupRolePermission = UserCurrentRole.permissions if not groupObject.settings.approval_required or not UserPermissions.invite_members: return abort(404) UserJoinRequest : GroupJoinRequest = GroupJoinRequest.query.filter_by(group_id=groupObject.id, user_id=userid).first() if UserJoinRequest is None: flash("Join request does not exist.", "error") return redirect(f"/groups/admin/{groupid}/members/requests") db.session.delete(UserJoinRequest) db.session.commit() return redirect(f"/groups/admin/{groupid}/members/requests") @groups_page.route("/groups/admin//members/requests/accept/", methods=['GET']) @auth.authenticated_required def groupadmin_members_requests_accept(groupid, userid): try: groupObject : Group = groups.GetGroupFromId(groupid) except groups.GroupExceptions.GroupDoesNotExist: return abort(404) AuthenticatedUser : User = auth.GetCurrentUser() UserCurrentRole : GroupRole = groups.GetUserRolesetInGroup(AuthenticatedUser, groupObject) UserPermissions : GroupRolePermission = UserCurrentRole.permissions if not groupObject.settings.approval_required or not UserPermissions.invite_members: return abort(404) UserJoinRequest : GroupJoinRequest = GroupJoinRequest.query.filter_by(group_id=groupObject.id, user_id=userid).first() if UserJoinRequest is None: flash("Join request does not exist.", "error") return redirect(f"/groups/admin/{groupid}/members/requests") UserGroupCount = groups.GetUserGroupCount(userid) UserMembership : MembershipType = membership.GetUserMembership(userid) GroupLimit = { MembershipType.NonBuildersClub: 5, MembershipType.BuildersClub: 10, MembershipType.TurboBuildersClub: 20, MembershipType.OutrageousBuildersClub: 100 } if UserGroupCount >= GroupLimit[UserMembership]: flash("This user has reached the maximum number of groups they can join.", "error") return redirect(f"/groups/admin/{groupid}/members/requests") db.session.delete(UserJoinRequest) db.session.commit() groups.AddUserToGroup(userid, groupObject, ForceJoin=True) return redirect(f"/groups/admin/{groupid}/members/requests") @groups_page.route("/groups/admin//members/requests/deny_all", methods=['POST']) @auth.authenticated_required def groupadmin_members_requests_deny_all(groupid): try: groupObject : Group = groups.GetGroupFromId(groupid) except groups.GroupExceptions.GroupDoesNotExist: return abort(404) AuthenticatedUser : User = auth.GetCurrentUser() UserCurrentRole : GroupRole = groups.GetUserRolesetInGroup(AuthenticatedUser, groupObject) UserPermissions : GroupRolePermission = UserCurrentRole.permissions if not groupObject.settings.approval_required or not UserPermissions.invite_members: return abort(404) AllJoinRequests : list[GroupJoinRequest] = GroupJoinRequest.query.filter_by(group_id=groupObject.id).all() for joinRequest in AllJoinRequests: db.session.delete(joinRequest) db.session.commit() return redirect(f"/groups/admin/{groupid}/members/requests") @groups_page.route("/groups/admin//info", methods=['GET']) @auth.authenticated_required def groupadmin_info(groupid): try: groupObject : Group = groups.GetGroupFromId(groupid) except groups.GroupExceptions.GroupDoesNotExist: return abort(404) AuthenticatedUser : User = auth.GetCurrentUser() if groupObject is None: return abort(404) AdminPermissionRequired(groupObject, AuthenticatedUser) UserCurrentRole : GroupRole = groups.GetUserRolesetInGroup(AuthenticatedUser, groupObject) UserPermissions : GroupRolePermission = UserCurrentRole.permissions return render_template("groups/admin_subpage/groupinfo.html", group=groupObject, user=AuthenticatedUser, role=UserCurrentRole, permissions=UserPermissions, page="info") @groups_page.route("/groups/admin//info", methods=['POST']) @auth.authenticated_required @limiter.limit("10/minute") def groupadmin_info_post(groupid): try: groupObject : Group = groups.GetGroupFromId(groupid) except groups.GroupExceptions.GroupDoesNotExist: return abort(404) AuthenticatedUser : User = auth.GetCurrentUser() if AuthenticatedUser.id != groupObject.owner_id: return abort(403) AdminPermissionRequired(groupObject, AuthenticatedUser) ImageFile = request.files.get("file") NewDescription = request.form.get("description") if ImageFile is not None and ImageFile.filename != "": if ImageFile.content_length > 1024 * 1024 * 2: # 2MB flash("Icon is too large, must be less than 2MB", "error") return redirect(f"/groups/admin/{groupid}/info") if ImageFile.content_type not in ["image/png", "image/jpeg"]: flash("Icon must be a PNG or JPEG image", "error") return redirect(f"/groups/admin/{groupid}/info") # Validate icon NewIcon = ValidateClothingImage( ImageFile, verifyResolution=False, validateFileSize=False, returnImage=True ) if NewIcon is False or NewIcon is None: flash("Icon is invalid", "error") return redirect(f"/groups/admin/{groupid}/info") if NewIcon.width != NewIcon.height: flash("Icon must be a square", "error") return redirect(f"/groups/admin/{groupid}/info") if NewIcon.width > 1024 or NewIcon.height > 1024: flash("Icon must be less than 1024x1024", "error") return redirect(f"/groups/admin/{groupid}/info") if NewIcon.width < 128 or NewIcon.height < 128: flash("Icon must be at least 128x128", "error") return redirect(f"/groups/admin/{groupid}/info") ImageFile = BytesIO() NewIcon.save(ImageFile, format="PNG") ImageFile.seek(0) IconImageHash = hashlib.sha512(ImageFile.read()).hexdigest() if not s3helper.DoesKeyExist(IconImageHash): ImageFile.seek(0) s3helper.UploadBytesToS3(ImageFile.read(), IconImageHash, contentType="image/png") groups.SetNewGroupIcon(groupObject, IconImageHash, AuthenticatedUser) flash("Group icon updated", "success") if NewDescription != groupObject.description: if len(NewDescription) > 1024: flash("Description must be less than 1024 characters", "error") return redirect(f"/groups/admin/{groupid}/info") if CountAlnumericCharacters(NewDescription) < 10: flash("Description must contain at least 10 alphanumeric characters", "error") return redirect(f"/groups/admin/{groupid}/info") NewLineCount = NewDescription.count("\n") if NewLineCount > 15: flash("Description must contain less than 15 newlines", "error") return redirect(f"/groups/admin/{groupid}/info") NewDescription = textfilter.FilterText(NewDescription) groupObject.description = NewDescription db.session.commit() flash("Group description updated", "success") return redirect(f"/groups/admin/{groupid}/info") @groups_page.route("/groups/admin//settings", methods=['GET']) @auth.authenticated_required def groupadmin_settings(groupid): try: groupObject : Group = groups.GetGroupFromId(groupid) except groups.GroupExceptions.GroupDoesNotExist: return abort(404) AuthenticatedUser : User = auth.GetCurrentUser() if groupObject is None: return abort(404) AdminPermissionRequired(groupObject, AuthenticatedUser) UserCurrentRole : GroupRole = groups.GetUserRolesetInGroup(AuthenticatedUser, groupObject) UserPermissions : GroupRolePermission = UserCurrentRole.permissions return render_template("groups/admin_subpage/settings.html", group=groupObject, user=AuthenticatedUser, role=UserCurrentRole, permissions=UserPermissions, page="settings") @groups_page.route("/groups/admin//settings", methods=['POST']) @auth.authenticated_required @limiter.limit("15/minute") def groupadmin_settings_post(groupid): try: groupObject : Group = groups.GetGroupFromId(groupid) except groups.GroupExceptions.GroupDoesNotExist: return abort(404) AuthenticatedUser : User = auth.GetCurrentUser() if groupObject is None: return abort(404) if groupObject.owner_id != AuthenticatedUser.id: return abort(403) approval_required = request.form.get("approval-required", "off", str) == "on" membership_required = request.form.get("membership-required", "off", str) == "on" enemy_declarations_allowed = request.form.get("declarations-allowed", "off", str) == "on" funds_visible = request.form.get("funds-visible", "off", str) == "on" games_visible = request.form.get("games-visible", "off", str) == "on" groupSettings : GroupSettings = groupObject.settings groupSettings.approval_required = approval_required groupSettings.membership_required = membership_required groupSettings.enemies_allowed = enemy_declarations_allowed groupSettings.funds_visible = funds_visible groupSettings.games_visible = games_visible if not approval_required: AllJoinRequests : list[GroupJoinRequest] = GroupJoinRequest.query.filter_by(group_id=groupObject.id).all() for joinRequest in AllJoinRequests: db.session.delete(joinRequest) db.session.commit() flash("Group settings updated", "success") return redirect(f"/groups/admin/{groupid}/settings") @groups_page.route('/groups/admin//roles', methods=["GET"]) @auth.authenticated_required def groupadmin_roles(groupid): try: groupObject : Group = groups.GetGroupFromId(groupid) except groups.GroupExceptions.GroupDoesNotExist: return abort(404) AuthenticatedUser : User = auth.GetCurrentUser() AdminPermissionRequired(groupObject, AuthenticatedUser) # Redirect to the top role TopRole : GroupRole = GroupRole.query.filter_by(group_id=groupObject.id).order_by(GroupRole.rank.desc()).first() return redirect(f"/groups/admin/{groupid}/roles/{TopRole.id}/view") @groups_page.route('/groups/admin//roles//view', methods=["GET"]) @auth.authenticated_required def groupadmin_role_view(groupid, roleid): try: groupObject : Group = groups.GetGroupFromId(groupid) except groups.GroupExceptions.GroupDoesNotExist: return abort(404) AuthenticatedUser : User = auth.GetCurrentUser() AdminPermissionRequired(groupObject, AuthenticatedUser) try: roleObject : GroupRole = groups.GetRolesetFromId(roleid) except groups.GroupExceptions.RolesetDoesNotExist: return abort(404) if roleObject.group_id != groupObject.id: return abort(404) UserCurrentRole : GroupRole = groups.GetUserRolesetInGroup(AuthenticatedUser, groupObject) UserPermissions : GroupRolePermission = UserCurrentRole.permissions AllGroupRoles : list[GroupRole] = GroupRole.query.filter_by(group_id=groupObject.id).order_by(GroupRole.rank.desc()).all() return render_template("groups/admin_subpage/roles.html", group=groupObject, user=AuthenticatedUser, roles=AllGroupRoles, page="roles", role=UserCurrentRole, permissions=UserPermissions, selectedrole = roleObject) @groups_page.route('/groups/admin//roles//update', methods=["POST"]) @auth.authenticated_required @limiter.limit("10/minute") def groupadmin_role_update(groupid, roleid): try: groupObject : Group = groups.GetGroupFromId(groupid) except groups.GroupExceptions.GroupDoesNotExist: return abort(404) AuthenticatedUser : User = auth.GetCurrentUser() if AuthenticatedUser.id != groupObject.owner_id: return abort(403) try: TargetRole : GroupRole = groups.GetRolesetFromId(roleid) except groups.GroupExceptions.RolesetDoesNotExist: return abort(404) if TargetRole.group_id != groupObject.id: return abort(404) RoleName = request.form.get("role-name", TargetRole.name, str) RoleDescription = request.form.get("role-description", TargetRole.description, str) RoleRank = request.form.get("role-rank", TargetRole.rank, int) ViewWallPermission = request.form.get("view_wall", "off", str) == "on" PostWallPermission = request.form.get("post_to_wall", "off", str) == "on" DeleteWallPermission = request.form.get("delete_from_wall", "off", str) == "on" ViewGroupShoutPermission = request.form.get("view_status", "off", str) == "on" PostGroupShoutPermission = request.form.get("post_to_status", "off", str) == "on" ManageMembersPermission = request.form.get("change_rank", "off", str) == "on" AcceptJoinsPermission = request.form.get("invite_members", "off", str) == "on" RemoveMembersPermission = request.form.get("remove_members", "off", str) == "on" CreateItemsPermission = request.form.get("create_items", "off", str) == "on" ManageItemsPermission = request.form.get("manage_items", "off", str) == "on" ManageGroupGamesPermission = request.form.get("manage_group_games", "off", str) == "on" ManageRelationshipsPermission = request.form.get("manage_relationships", "off", str) == "on" ViewAuditLogsPermission = request.form.get("view_audit_logs", "off", str) == "on" if TargetRole.rank == 0: if RoleName != TargetRole.name or RoleDescription != TargetRole.description or RoleRank != TargetRole.rank: return abort(400) if CreateItemsPermission or ManageItemsPermission or ManageGroupGamesPermission: flash("You cannot give the guest role Asset permissions.", "error") return redirect(f"/groups/admin/{groupid}/roles/{roleid}/view") FilteredRoleName = textfilter.FilterText(RoleName) FilteredRoleDescription = textfilter.FilterText(RoleDescription) if len(FilteredRoleName) > 32: flash("Role name must be less than 32 characters", "error") return redirect(f"/groups/admin/{groupid}/roles/{roleid}/view") if len(FilteredRoleDescription) > 256: flash("Role description must be less than 256 characters", "error") return redirect(f"/groups/admin/{groupid}/roles/{roleid}/view") if CountAlnumericCharacters(FilteredRoleName) < 3: flash("Role name must contain at least 3 alphanumeric characters", "error") return redirect(f"/groups/admin/{groupid}/roles/{roleid}/view") if RoleRank < 1 or RoleRank > 254: if TargetRole.rank not in [0, 255]: flash("Role rank must be between 1 and 254. 0 and 255 are reserved for guests and the owner.", "error") return redirect(f"/groups/admin/{groupid}/roles/{roleid}/view") if TargetRole.rank != 0: TargetRole.name = FilteredRoleName TargetRole.description = FilteredRoleDescription if TargetRole.rank not in [0,255]: TargetRole.rank = RoleRank if TargetRole.rank != 255: groups.ModifyRolesetPermission( TargetRole, ViewWall=ViewWallPermission, PostToWall=PostWallPermission, DeleteFromWall=DeleteWallPermission, ViewStatus=ViewGroupShoutPermission, PostToStatus=PostGroupShoutPermission, ChangeRank=ManageMembersPermission, InviteMembers=AcceptJoinsPermission, RemoveMembers=RemoveMembersPermission, CreateItems=CreateItemsPermission, ManageItems=ManageItemsPermission, ManageGroupGames=ManageGroupGamesPermission, ManageRelationships=ManageRelationshipsPermission, ViewAuditLogs=ViewAuditLogsPermission ) db.session.commit() flash("Role updated", "success") return redirect(f"/groups/admin/{groupid}/roles/{roleid}/view") @groups_page.route('/groups/admin//roles/create', methods=["GET"]) @auth.authenticated_required def groupadmin_role_create(groupid): try: groupObject : Group = groups.GetGroupFromId(groupid) except groups.GroupExceptions.GroupDoesNotExist: return abort(404) AuthenticatedUser : User = auth.GetCurrentUser() if AuthenticatedUser.id != groupObject.owner_id: return abort(403) UserCurrentRole : GroupRole = groups.GetUserRolesetInGroup(AuthenticatedUser, groupObject) UserPermissions : GroupRolePermission = UserCurrentRole.permissions return render_template("groups/admin_subpage/create_role.html", group=groupObject, user=AuthenticatedUser, page="roles", role=UserCurrentRole, permissions=UserPermissions) @groups_page.route('/groups/admin//roles/create', methods=["POST"]) @auth.authenticated_required @limiter.limit("10/minute") def groupadmin_role_create_post(groupid): try: groupObject : Group = groups.GetGroupFromId(groupid) except groups.GroupExceptions.GroupDoesNotExist: return abort(404) AuthenticatedUser : User = auth.GetCurrentUser() if AuthenticatedUser.id != groupObject.owner_id: return abort(403) RoleName = request.form.get("name", None, str) RoleDescription = request.form.get("description", None, str) RoleRank = request.form.get("rank", None, int) if RoleName is None or RoleDescription is None or RoleRank is None: return abort(400) if RoleRank < 1 or RoleRank > 254: flash("Role rank must be between 1 and 254. 0 and 255 are reserved for guests and the owner.", "error") return redirect(f"/groups/admin/{groupid}/roles/create") if len(RoleName) > 32: flash("Role name must be less than 32 characters", "error") return redirect(f"/groups/admin/{groupid}/roles/create") if len(RoleDescription) > 256: flash("Role description must be less than 256 characters", "error") return redirect(f"/groups/admin/{groupid}/roles/create") if CountAlnumericCharacters(RoleName) < 3: flash("Role name must contain at least 3 alphanumeric characters", "error") return redirect(f"/groups/admin/{groupid}/roles/create") GroupRoleCount = GroupRole.query.filter_by(group_id=groupObject.id).count() if GroupRoleCount >= 255: flash("You cannot create more than 255 roles. ( Why do you even need that many!? )", "error") return redirect(f"/groups/admin/{groupid}/roles/create") RobuxBalance, _ = economy.GetUserBalance(AuthenticatedUser) if RobuxBalance < 25: flash("You do not have enough robux to create a new role.", "error") return redirect(f"/groups/admin/{groupid}/roles/create") economy.DecrementTargetBalance(AuthenticatedUser, 25, 0) transactions.CreateTransaction( Reciever = User.query.filter_by(id=1).first(), Sender = AuthenticatedUser, CurrencyAmount = 25, CurrencyType = 0, TransactionType = TransactionType.Purchase, AssetId = None, CustomText = "Created Group Role" ) FilteredRoleName = textfilter.FilterText(RoleName) FilteredRoleDescription = textfilter.FilterText(RoleDescription) NewRole : GroupRole = groups.CreateGroupRoleset(groupObject, FilteredRoleName, FilteredRoleDescription, RoleRank) groups.ModifyRolesetPermission( NewRole, ViewWall=True, PostToWall=True, ViewStatus=True ) return redirect(f"/groups/admin/{groupid}/roles/{NewRole.id}/view") @groups_page.route('/groups/admin//payouts', methods=["GET"]) @auth.authenticated_required def groupadmin_payout_user(groupid): try: groupObject : Group = groups.GetGroupFromId(groupid) except groups.GroupExceptions.GroupDoesNotExist: return abort(404) AuthenticatedUser : User = auth.GetCurrentUser() if AuthenticatedUser.id != groupObject.owner_id: return abort(403) UserCurrentRole : GroupRole = groups.GetUserRolesetInGroup(AuthenticatedUser, groupObject) UserPermissions : GroupRolePermission = UserCurrentRole.permissions return render_template("groups/admin_subpage/payout.html", group=groupObject, user=AuthenticatedUser, page="payouts", role=UserCurrentRole, permissions=UserPermissions) @groups_page.route('/groups/admin//payouts/one-time', methods=["POST"]) @auth.authenticated_required @limiter.limit("15/minute") def groupadmin_payout_onetime_user_post( groupid : int ): try: groupObject : Group = groups.GetGroupFromId(groupid) except groups.GroupExceptions.GroupDoesNotExist: return abort(404) AuthenticatedUser : User = auth.GetCurrentUser() if AuthenticatedUser.id != groupObject.owner_id: return abort(403) UserCurrentRole : GroupRole = groups.GetUserRolesetInGroup(AuthenticatedUser, groupObject) UserPermissions : GroupRolePermission = UserCurrentRole.permissions payoutCurrency : str = request.form.get( key = "payout-currency", default = None, type = str ) payoutAmount : int = request.form.get( key = "payout-amount", default = None, type = int ) payoutRecipientUsername : str = request.form.get( key = "payout-recipient", default = None, type = str ) if payoutCurrency is None or payoutAmount is None or payoutRecipientUsername is None: return abort(400) if payoutCurrency not in ["robux", "tickets"]: return abort(400) if payoutAmount < 1: flash("Payout amount must be greater than 0", "error") return redirect(f"/groups/admin/{groupid}/payouts") if len(payoutRecipientUsername) > 128: flash("User does not exist or username is not correct", "error") return redirect(f"/groups/admin/{groupid}/payouts") payoutRecipient : User = User.query.filter_by(username=payoutRecipientUsername).first() if payoutRecipient is None: flash("User does not exist or username is not correct", "error") return redirect(f"/groups/admin/{groupid}/payouts") if payoutRecipient.accountstatus != 1: flash("User has a active ban", "error") return redirect(f"/groups/admin/{groupid}/payouts") if groups.GetUserRankInGroup(payoutRecipient, groupObject) == 0: flash("User is not in the group", "error") return redirect(f"/groups/admin/{groupid}/payouts") robuxBalance, ticketsBalance = economy.GetGroupBalance( groupObject ) if payoutCurrency == "robux": if robuxBalance < payoutAmount: flash("Group does not have enough robux to payout", "error") return redirect(f"/groups/admin/{groupid}/payouts") elif payoutCurrency == "tickets": if ticketsBalance < payoutAmount: flash("Group does not have enough tickets to payout", "error") return redirect(f"/groups/admin/{groupid}/payouts") try: economy.TransferFunds( Source = groupObject, Target = payoutRecipient, Amount = payoutAmount, CurrencyType = 0 if payoutCurrency == "robux" else 1, ApplyTax = False ) except economy.InsufficientFundsException: flash("Group does not have enough funds to payout", "error") return redirect(f"/groups/admin/{groupid}/payouts") except Exception as e: logging.error(f"groupadmin_payout_onetime_user_post : An error occured while trying to payout: {e}") flash("An error occured while trying to payout, please contact support", "error") return redirect(f"/groups/admin/{groupid}/payouts") try: transactions.CreateTransaction( Reciever = payoutRecipient, Sender = groupObject, CurrencyAmount = payoutAmount, CurrencyType = 0 if payoutCurrency == "robux" else 1, TransactionType = TransactionType.GroupPayout, AssetId = None, CustomText = f"Group Payout from {AuthenticatedUser.username}" ) except Exception as e: logging.error(f"groupadmin_payout_onetime_user_post : An error occured while trying to create a transaction: {e}") flash(f"Successfully paid {payoutRecipient.username} {payoutAmount} {payoutCurrency}", "success") return redirect(f"/groups/admin/{groupid}/payouts") @groups_page.route('/groups//') @groups_page.route('/groups//') @auth.authenticated_required def groupview(groupid, slug=""): try: groupObject : Group = groups.GetGroupFromId(groupid) except groups.GroupExceptions.GroupDoesNotExist: return abort(404) SlugName = slugify(groupObject.name, lowercase=False) if SlugName is None or SlugName == "": SlugName = "unnamed" if slug != SlugName: PageNumber = request.args.get( key = "page", default=None, type=int) if PageNumber is None: return redirect(f"/groups/{groupid}/{SlugName}") else: return redirect(f"/groups/{groupid}/{SlugName}?page={PageNumber}") AuthenticatedUser : User = auth.GetCurrentUser() UserCurrentRole : GroupRole = groups.GetUserRolesetInGroup(AuthenticatedUser, groupObject) GroupWall = [] if UserCurrentRole.permissions.view_wall: PageNumber : int = request.args.get( key = "page", default=1, type=int) if PageNumber < 1: PageNumber = 1 GroupWall = GroupWallPost.query.filter_by(group_id=groupObject.id).order_by(GroupWallPost.created_at.desc()).paginate( page=PageNumber, per_page=10, error_out=False) GroupRoles : list[GroupRole] = groupObject.roles GroupRoles.sort(key=lambda x: x.rank, reverse=False) UserPermissions : GroupRolePermission = UserCurrentRole.permissions isGroupAdmin = UserPermissions.change_rank or \ UserPermissions.manage_clan or \ UserPermissions.manage_relationships or \ UserPermissions.remove_members or \ UserPermissions.spend_group_funds CurrentJoinRequest : GroupJoinRequest | None = groups.GetJoinRequest(AuthenticatedUser, groupObject) return render_template( 'groups/view.html', groupObj=groupObject, groupservice = groups, UserCurrentRole=UserCurrentRole, UserGroups=GetUserGroups(AuthenticatedUser), GroupWall=GroupWall, GroupRoles=GroupRoles, GroupStatus=groups.GetLatestGroupStatus(groupObject), PageNumber=PageNumber, isThereNextPage=GroupWall.has_next, isTherePreviousPage=GroupWall.has_prev, slug=SlugName, isGroupAdmin=isGroupAdmin, CurrentJoinRequest=CurrentJoinRequest) @groups_page.errorhandler(429) def RateLimitReached(e): if request.headers.get("Accept", default="text/html") == "application/json": return jsonify({ "error": "You are being rate limited. Please try again later.", "success": False }), 429 flash("You are being rate limited. Please try again later.", "error") return make_response(redirect(request.referrer), 429) @groups_page.route('/groups/join/', methods=["POST"]) @limiter.limit("5/minute", on_breach=RateLimitReached) @auth.authenticated_required def groupjoin(groupid): try: groupObject : Group = groups.GetGroupFromId(groupid) except groups.GroupExceptions.GroupDoesNotExist: return abort(404) if not websiteFeatures.GetWebsiteFeature("GroupJoining"): flash("Group joining is temporarily disabled!", "error") return redirect(f"/groups/{groupid}/") if groupObject.locked: flash("This group is locked!", "error") return redirect(f"/groups/{groupid}/") AuthenticatedUser : User = auth.GetCurrentUser() if groups.GetUserRankInGroup(AuthenticatedUser, groupObject) != 0: flash("You are already in this group!", "error") return redirect(f"/groups/{groupid}/") ExisitingJoinRequest : GroupJoinRequest = groups.GetJoinRequest(AuthenticatedUser, groupObject) if ExisitingJoinRequest is not None: flash("You have already requested to join this group!", "error") return redirect(f"/groups/{groupid}/") UserMembership : MembershipType = membership.GetUserMembership(AuthenticatedUser) if groupObject.settings.membership_required: if UserMembership == MembershipType.NonBuildersClub: flash("You need to have a Builders Club membership to join this group!", "error") return redirect(f"/groups/{groupid}/") UserGroupCount = groups.GetUserGroupCount(AuthenticatedUser) GroupLimit = { MembershipType.NonBuildersClub: 5, MembershipType.BuildersClub: 10, MembershipType.TurboBuildersClub: 20, MembershipType.OutrageousBuildersClub: 100 } if UserGroupCount >= GroupLimit[UserMembership]: flash("You have reached the maximum number of groups you can join!", "error") return redirect(f"/groups/{groupid}/") JoinResponse : GroupMember | GroupJoinRequest | None = groups.AddUserToGroup( TargetUser=AuthenticatedUser, TargetGroup=groupObject, ForceJoin=False) if JoinResponse is None: flash("An error occured while trying to join this group!", "error") return redirect(f"/groups/{groupid}/") if isinstance(JoinResponse, GroupJoinRequest): flash("Your join request has been sent!", "success") return redirect(f"/groups/{groupid}/") flash("Joined group successfully!", "success") return redirect(f"/groups/{groupid}/") @groups_page.route('/groups/leave/', methods=["POST"]) @auth.authenticated_required def groupleave(groupid): try: groupObject : Group = groups.GetGroupFromId(groupid) except groups.GroupExceptions.GroupDoesNotExist: return abort(404) if not websiteFeatures.GetWebsiteFeature("GroupLeaving"): flash("Group leaving is temporarily disabled!", "error") return redirect(f"/groups/{groupid}/") AuthenticatedUser : User = auth.GetCurrentUser() if groups.GetUserRankInGroup(AuthenticatedUser, groupObject) == 0: CurrentJoinRequest : GroupJoinRequest | None = groups.GetJoinRequest(AuthenticatedUser, groupObject) if CurrentJoinRequest is not None: db.session.delete(CurrentJoinRequest) db.session.commit() flash("Your join request has been cancelled!", "success") return redirect(f"/groups/{groupid}/") flash("You are not in this group!", "error") return redirect(f"/groups/{groupid}/") if groupObject.owner_id == AuthenticatedUser.id: flash("Owners cannot leave their group for now", "error") return redirect(f"/groups/{groupid}/") LeaveResponse : bool = groups.RemoveUserFromGroup( TargetUser=AuthenticatedUser, TargetGroup=groupObject) if not LeaveResponse: flash("An error occured while trying to leave this group!", "error") return redirect(f"/groups/{groupid}/") flash("Left group successfully!", "success") return redirect(f"/groups/{groupid}/") @groups_page.route('/groups/members_json/', methods=["GET"]) @groups_page.route('/groups//members_json', methods=["GET"]) @auth.authenticated_required_api @limiter.limit("5/second") def groupmembers_json(groupid): try: groupObject : Group = groups.GetGroupFromId(groupid) except groups.GroupExceptions.GroupDoesNotExist: return abort(404) RolesetId : int = request.args.get( key ="role", default=None, type=int) PageNumber : int = request.args.get( key = "page", default=1, type=int) if PageNumber < 1: PageNumber = 1 try: RolesetObject : GroupRole = groups.GetRolesetFromId(RolesetId) except groups.GroupExceptions.RolesetDoesNotExist: return abort(404) if RolesetObject.group_id != groupObject.id: return abort(404) Members : list[GroupMember] = GroupMember.query.filter_by( group_id=groupObject.id, group_role_id=RolesetObject.id ).order_by(GroupMember.created_at.desc()).paginate( page=PageNumber, per_page=10, error_out=False) MembersList = [] for Member in Members.items: MembersList.append({ "userId": Member.user_id, "username": Member.user.username, }) return jsonify({ "users": MembersList, "nextpage": Members.has_next }) @groups_page.route('/groups/wall_post/', methods=["POST"]) @auth.authenticated_required @limiter.limit("5/minute", on_breach=RateLimitReached) def PostToGroupWall( groupid : int ): try: groupObject : Group = groups.GetGroupFromId(groupid) except groups.GroupExceptions.GroupDoesNotExist: return abort(404) AuthenticatedUser : User = auth.GetCurrentUser() try: groups.AssertUserHasPermission(AuthenticatedUser, groupObject, GroupRolePermission.post_to_wall) except groups.GroupExceptions.InsufficientPermssions: flash("You do not have permission to post to this group's wall!", "error") return redirect(f"/groups/{groupid}/") if not websiteFeatures.GetWebsiteFeature("GroupWallPosting"): flash("Group Wall Posting is temporarily disabled!", "error") return redirect(f"/groups/{groupid}/") PostContent : str = request.form.get("post_content", default=None, type=str) if PostContent is None or PostContent == "": flash("Please fill in all fields", "error") return redirect(f"/groups/{groupid}/") if len(PostContent) > 512: flash("Your post cannot be longer than 512 characters!", "error") return redirect(f"/groups/{groupid}/") if CountAlnumericCharacters(PostContent) < 3: flash("Your post must contain at least 3 alphanumeric characters!", "error") return redirect(f"/groups/{groupid}/") if len(PostContent.split("\n")) > 10: flash("Your post cannot contain more than 10 lines!", "error") return redirect(f"/groups/{groupid}/") PostResponse : GroupWallPost | None = groups.PostToGroupWall(AuthenticatedUser, groupObject, PostContent) if PostResponse is None: flash("An error occured while trying to post to this group's wall!", "error") return redirect(f"/groups/{groupid}/") return redirect(f"/groups/{groupid}/") @groups_page.route('/groups//delete_post/', methods=["POST"]) @auth.authenticated_required @csrf.exempt @limiter.limit("10/minute", on_breach=RateLimitReached) def DeleteGroupWallPost( groupid : int, postid : int ): try: groupObject : Group = groups.GetGroupFromId(groupid) except groups.GroupExceptions.GroupDoesNotExist: return abort(404) PostObject : GroupWallPost = GroupWallPost.query.filter_by(id=postid, group_id=groupObject.id).first() if PostObject is None: return abort(404) AuthenticatedUser : User = auth.GetCurrentUser() try: groups.AssertUserHasPermission(AuthenticatedUser, groupObject, GroupRolePermission.delete_from_wall) except groups.GroupExceptions.InsufficientPermssions: if PostObject.poster_id != AuthenticatedUser.id: flash("You do not have permission to delete this post!", "error") return redirect(f"/groups/{groupid}/") groups.DeleteGroupWallPost(PostObject, AuthenticatedUser, ForceDelete=True) flash("Deleted post successfully!", "success") return redirect(f"/groups/{groupid}/") @groups_page.route("/groups/update_status/", methods=["POST"]) @auth.authenticated_required @limiter.limit("5/minute", on_breach=RateLimitReached) def UpdateGroupStatus( groupid : int ): try: groupObject : Group = groups.GetGroupFromId(groupid) except groups.GroupExceptions.GroupDoesNotExist: return abort(404) AuthenticatedUser : User = auth.GetCurrentUser() try: groups.AssertUserHasPermission(AuthenticatedUser, groupObject, GroupRolePermission.post_to_status) except groups.GroupExceptions.InsufficientPermssions: flash("You do not have permission to update this group's status!", "error") return redirect(f"/groups/{groupid}/") OriginalStatus : str = request.form.get( key="status", default=None, type=str ) if OriginalStatus is None: flash("Please fill in all fields!", "error") return redirect(f"/groups/{groupid}/") if len(OriginalStatus) > 255: flash("Your status cannot be longer than 255 characters!", "error") return redirect(f"/groups/{groupid}/") if CountAlnumericCharacters(OriginalStatus) < 3 and OriginalStatus != "": flash("Your status must contain at least 3 alphanumeric characters!", "error") return redirect(f"/groups/{groupid}/") FilteredStatus : str = textfilter.FilterText(OriginalStatus) groups.PostToGroupStatus( AuthenticatedUser, groupObject, FilteredStatus ) flash("Updated group status successfully!", "success") return redirect(f"/groups/{groupid}/") @groups_page.route("/groups/create", methods=["GET"]) @auth.authenticated_required def CreateGroupPage(): if not websiteFeatures.GetWebsiteFeature("GroupCreation"): flash("Group creation is temporarily disabled!", "error") return render_template("groups/create.html") return render_template("groups/create.html") @groups_page.route("/groups/create", methods=["POST"]) @auth.authenticated_required def CreateGroupPage_post(): AuthenticatedUser : User = auth.GetCurrentUser() if not websiteFeatures.GetWebsiteFeature("GroupCreation"): flash("Group creation is temporarily disabled!", "error") return redirect("/groups/create") GroupName : str = request.form.get("name", default=None, type=str) GroupDescription : str = request.form.get("description", default=None, type=str) IconImage = request.files.get("icon", default=None) if GroupName is None or GroupDescription is None or IconImage is None: flash("Please fill in all fields!", "error") return redirect("/groups/create") GroupName = GroupName.strip() if len(GroupName) > 30: flash("Group name cannot be longer than 30 characters!", "error") return redirect("/groups/create") if len(GroupDescription) > 1024: flash("Group description cannot be longer than 1024 characters!", "error") return redirect("/groups/create") if CountAlnumericCharacters(GroupName) < 3: flash("Group name must contain at least 3 alphanumeric characters!", "error") return redirect("/groups/create") if CountAlnumericCharacters(GroupDescription) < 10: flash("Group description must contain at least 10 alphanumeric characters!", "error") return redirect("/groups/create") if len(GroupDescription.split("\n")) > 15: flash("Group description cannot contain more than 15 lines!", "error") return redirect("/groups/create") if not GroupName[0].isalnum(): flash("Group name must start with a alphanumeric character!", "error") return redirect("/groups/create") for char in GroupName: if char not in allowedCharacters: flash("Group name contains invalid characters!", "error") return redirect("/groups/create") if char == " " and GroupName[GroupName.index(char) + 1] == " ": flash("Group name cannot contain two or more consecutive spaces!", "error") return redirect("/groups/create") if IconImage.filename == "": flash("Please upload an icon!", "error") return redirect("/groups/create") if IconImage.content_length > 1024 * 1024 * 2: flash("Icon cannot be larger than 2MB!", "error") return redirect("/groups/create") if IconImage.content_type not in ["image/png", "image/jpeg"]: flash("Icon must be a PNG or JPEG image", "error") return redirect("/groups/create") NewIcon = ValidateClothingImage( IconImage, verifyResolution=False, validateFileSize=False, returnImage=True ) if NewIcon is False or NewIcon is None: flash("Icon is invalid", "error") return redirect("/groups/create") if NewIcon.width != NewIcon.height: flash("Icon must be a square", "error") return redirect("/groups/create") if NewIcon.width > 1024 or NewIcon.height > 1024: flash("Icon must be less than 1024x1024", "error") return redirect("/groups/create") if NewIcon.width < 128 or NewIcon.height < 128: flash("Icon must be at least 128x128", "error") return redirect("/groups/create") UserMembership : MembershipType = membership.GetUserMembership(AuthenticatedUser) UserGroupCount = groups.GetUserGroupCount(AuthenticatedUser) GroupLimit = { MembershipType.NonBuildersClub: 5, MembershipType.BuildersClub: 10, MembershipType.TurboBuildersClub: 20, MembershipType.OutrageousBuildersClub: 100 } if UserGroupCount >= GroupLimit[UserMembership]: flash("You have reached the maximum number of groups you can join!", "error") return redirect("/groups/create") try: textfilter.FilterText(GroupName, ThrowException=True) except textfilter.TextNotAllowedException: flash("Group name is not safe for SYNTAX!", "error") return redirect("/groups/create") ExistingGroup : Group = groups.SearchGroupByName(GroupName) if ExistingGroup is not None: flash("Group name is already taken!", "error") return redirect("/groups/create") FilteredGroupDescription : str = textfilter.FilterText(GroupDescription) RobuxBalance, _ = economy.GetUserBalance(AuthenticatedUser) if RobuxBalance < 100: flash("You do not have enough robux to create a group.", "error") return redirect("/groups/create") economy.DecrementTargetBalance(AuthenticatedUser, 100, 0) transactions.CreateTransaction( Reciever = User.query.filter_by(id=1).first(), Sender = AuthenticatedUser, CurrencyAmount = 100, CurrencyType = 0, TransactionType = TransactionType.Purchase, AssetId = None, CustomText = "Created Group" ) IconImage = BytesIO() NewIcon.save(IconImage, format="PNG") IconImage.seek(0) IconImageHash = hashlib.sha512(IconImage.read()).hexdigest() if not s3helper.DoesKeyExist(IconImageHash): IconImage.seek(0) s3helper.UploadBytesToS3(IconImage.read(), IconImageHash, contentType="image/png") NewGroup : Group = groups.CreateGroup(GroupName, FilteredGroupDescription, AuthenticatedUser, IconImageHash) return redirect(f"/groups/{NewGroup.id}/") from sqlalchemy import func @groups_page.route("/groups/search", methods=["GET"]) @auth.authenticated_required def SearchGroupsPage(): PageNumber : int = request.args.get( key = "page", default=1, type=int) if PageNumber < 1: PageNumber = 1 Query : str = request.args.get( key = "query", default="", type=str) if len(Query) > 32: Query = Query[:32] if len(Query) < 3: Query = "" GroupQuery = Group.query if Query != "": GroupQuery = GroupQuery.filter(Group.name.ilike(f"%{Query}%")) GroupQuery = GroupQuery.outerjoin(GroupRole, GroupRole.group_id == Group.id ).group_by(Group.id).order_by(func.coalesce(func.sum(GroupRole.member_count), 0).desc()).order_by(Group.created_at.desc()) GroupQuery = GroupQuery.paginate( page=PageNumber, per_page=12, error_out=False) return render_template("groups/search.html", query=Query, groups=GroupQuery, groupservice=groups) @groups_page.route("/groups/search", methods=["POST"]) @auth.authenticated_required @csrf.exempt def SearchGroupsPage_post(): Query : str = request.form.get("query", default="", type=str) if len(Query) > 32: Query = Query[:32] if len(Query) < 3: Query = "" if Query == "": return redirect("/groups/search") return redirect(f"/groups/search?query={Query}") @groups_page.route("/groups", methods=["GET"]) @auth.authenticated_required def GroupsPage(): AuthenticatedUser : User = auth.GetCurrentUser() FirstGroup : GroupMember = GroupMember.query.filter_by(user_id=AuthenticatedUser.id).order_by(GroupMember.created_at.asc()).first() if FirstGroup is None: return redirect("/groups/search") return redirect(f"/groups/{FirstGroup.group_id}/")