From 2861f721afa097882d947d8818fb26201e641aa2 Mon Sep 17 00:00:00 2001 From: Divided by Zer0 Date: Tue, 12 Sep 2023 01:19:54 +0200 Subject: [PATCH] feat: Reports (#20) * feat: added reports * reports working * changelog --- CHANGELOG.md | 6 +++- fediseer/apis/models/v1.py | 8 +++++ fediseer/apis/v1/__init__.py | 2 ++ fediseer/apis/v1/censures.py | 23 +++++++++++++ fediseer/apis/v1/endorsements.py | 16 +++++++++ fediseer/apis/v1/guarantees.py | 16 +++++++++ fediseer/apis/v1/report.py | 56 ++++++++++++++++++++++++++++++++ fediseer/classes/__init__.py | 1 + fediseer/classes/reports.py | 16 +++++++++ fediseer/consts.py | 2 +- fediseer/database/functions.py | 24 ++++++++++++++ fediseer/enums.py | 12 +++++++ 12 files changed, 180 insertions(+), 2 deletions(-) create mode 100644 fediseer/apis/v1/report.py create mode 100644 fediseer/classes/reports.py create mode 100644 fediseer/enums.py diff --git a/CHANGELOG.md b/CHANGELOG.md index ec5e7b7..98a96fc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog -# 0.8.1 +# 0.10.0 + +Added `/api/v1/reports` endpoint which can show and filter recent actions on the fediseer + +# 0.9.1 Added sysadmins and moderators count. This allows instance admins to self-report how many people they have in sysadmins positions and how many in moderator positions. This might be useful to find which instances might be lacking in support for their user-base. diff --git a/fediseer/apis/models/v1.py b/fediseer/apis/models/v1.py index 1e37fe8..ffc08cf 100644 --- a/fediseer/apis/models/v1.py +++ b/fediseer/apis/models/v1.py @@ -1,4 +1,5 @@ from flask_restx import fields +from fediseer import enums class Models: def __init__(self,api): @@ -65,3 +66,10 @@ class Models: 'sysadmins': fields.Integer(required=False, default=None, min=0, max=100, description="Report how many system administrators this instance currently has."), 'moderators': fields.Integer(required=False, default=None, min=0, max=1000, description="Report how many instance moderators this instance currently has."), }) + self.response_model_reports = api.model('ActivityReport', { + 'source_domain': fields.String(description="The instance domain which initiated this activity", example="lemmy.dbzer0.com"), + 'target_domain': fields.String(description="The instance domain which was the target of this activity", example="lemmy.dbzer0.com"), + 'report_type': fields.String(description="The type of report activity", enum=[e.name for e in enums.ReportType]), + 'report_activity': fields.String(description="The activity reported", enum=[e.name for e in enums.ReportActivity]), + 'created': fields.DateTime(description="The date this record was added"), + }) \ No newline at end of file diff --git a/fediseer/apis/v1/__init__.py b/fediseer/apis/v1/__init__.py index 650becc..a76f3ca 100644 --- a/fediseer/apis/v1/__init__.py +++ b/fediseer/apis/v1/__init__.py @@ -6,6 +6,7 @@ import fediseer.apis.v1.guarantees as guarantees import fediseer.apis.v1.activitypub as activitypub import fediseer.apis.v1.badges as badges import fediseer.apis.v1.find as find +import fediseer.apis.v1.report as report from fediseer.apis.v1.base import api api.add_resource(base.Suspicions, "/instances") @@ -22,3 +23,4 @@ api.add_resource(guarantees.Guarantors, "/guarantors/") api.add_resource(guarantees.Guarantees, "/guarantees/") api.add_resource(badges.GuaranteeBadge, "/badges/guarantees/.svg") api.add_resource(badges.EndorsementBadge, "/badges/endorsements/.svg") +api.add_resource(report.Report, "/reports/") diff --git a/fediseer/apis/v1/censures.py b/fediseer/apis/v1/censures.py index cd3b7b1..ea42b2c 100644 --- a/fediseer/apis/v1/censures.py +++ b/fediseer/apis/v1/censures.py @@ -1,6 +1,8 @@ from fediseer.apis.v1.base import * from fediseer.classes.instance import Censure,Endorsement from fediseer.utils import sanitize_string +from fediseer.classes.reports import Report +from fediseer import enums class CensuresGiven(Resource): get_parser = reqparse.RequestParser() @@ -135,6 +137,13 @@ class Censures(Resource): reason=reason, ) db.session.add(new_censure) + new_report = Report( + source_domain=instance.domain, + target_domain=target_instance.domain, + report_type=enums.ReportType.CENSURE, + report_activity=enums.ReportActivity.ADDED, + ) + db.session.add(new_report) db.session.commit() logger.info(f"{instance.domain} Censured {domain}") return {"message":'Changed'}, 200 @@ -174,6 +183,13 @@ class Censures(Resource): if censure.reason == reason: return {"message":'OK'}, 200 censure.reason = reason + new_report = Report( + source_domain=instance.domain, + target_domain=target_instance.domain, + report_type=enums.ReportType.CENSURE, + report_activity=enums.ReportActivity.MODIFIED, + ) + db.session.add(new_report) db.session.commit() logger.info(f"{instance.domain} Modfied censure for {domain}") return {"message":'Changed'}, 200 @@ -204,6 +220,13 @@ class Censures(Resource): if not censure: return {"message":'OK'}, 200 db.session.delete(censure) + new_report = Report( + source_domain=instance.domain, + target_domain=target_instance.domain, + report_type=enums.ReportType.CENSURE, + report_activity=enums.ReportActivity.DELETED, + ) + db.session.add(new_report) db.session.commit() logger.info(f"{instance.domain} Withdrew censure from {domain}") return {"message":'Changed'}, 200 \ No newline at end of file diff --git a/fediseer/apis/v1/endorsements.py b/fediseer/apis/v1/endorsements.py index 6e7a9f7..1e30126 100644 --- a/fediseer/apis/v1/endorsements.py +++ b/fediseer/apis/v1/endorsements.py @@ -1,5 +1,7 @@ from fediseer.apis.v1.base import * from fediseer.classes.instance import Endorsement,Censure +from fediseer.classes.reports import Report +from fediseer import enums class Approvals(Resource): get_parser = reqparse.RequestParser() @@ -100,6 +102,13 @@ class Endorsements(Resource): endorsed_id=target_instance.id, ) db.session.add(new_endorsement) + new_report = Report( + source_domain=instance.domain, + target_domain=target_instance.domain, + report_type=enums.ReportType.ENDORSEMENT, + report_activity=enums.ReportActivity.ADDED, + ) + db.session.add(new_report) db.session.commit() if not database.has_recent_endorsement(target_instance.id): activitypub_pm.pm_admins( @@ -137,6 +146,13 @@ class Endorsements(Resource): if not endorsement: return {"message":'OK'}, 200 db.session.delete(endorsement) + new_report = Report( + source_domain=instance.domain, + target_domain=target_instance.domain, + report_type=enums.ReportType.ENDORSEMENT, + report_activity=enums.ReportActivity.DELETED, + ) + db.session.add(new_report) db.session.commit() activitypub_pm.pm_admins( message=f"Oh no. {instance.domain} has just withdrawn the endorsement of your instance", diff --git a/fediseer/apis/v1/guarantees.py b/fediseer/apis/v1/guarantees.py index eb59733..dc68dbe 100644 --- a/fediseer/apis/v1/guarantees.py +++ b/fediseer/apis/v1/guarantees.py @@ -1,5 +1,7 @@ from fediseer.apis.v1.base import * from fediseer.classes.instance import Guarantee, Endorsement, RejectionRecord +from fediseer.classes.reports import Report +from fediseer import enums class Guarantors(Resource): get_parser = reqparse.RequestParser() @@ -103,6 +105,13 @@ class Guarantees(Resource): # endorsed_id=target_instance.id, # ) # db.session.add(new_endorsement) + new_report = Report( + source_domain=instance.domain, + target_domain=target_instance.domain, + report_type=enums.ReportType.GUARANTEE, + report_activity=enums.ReportActivity.ADDED, + ) + db.session.add(new_report) db.session.commit() activitypub_pm.pm_admins( message=f"Congratulations! Your instance has just been [guaranteed](https://fediseer.com/faq#what-is-a-guarantee) by {instance.domain}. \n\nThis is an automated PM by the [Fediseer](https://fediseer.com) service. Replies will not be read.\nPlease contact @db0@lemmy.dbzer0.com for further inquiries.", @@ -167,6 +176,13 @@ class Guarantees(Resource): rejector_id=instance.id, ) db.session.add(rejection) + new_report = Report( + source_domain=instance.domain, + target_domain=target_instance.domain, + report_type=enums.ReportType.GUARANTEE, + report_activity=enums.ReportActivity.DELETED, + ) + db.session.add(new_report) db.session.commit() try: activitypub_pm.pm_admins( diff --git a/fediseer/apis/v1/report.py b/fediseer/apis/v1/report.py new file mode 100644 index 0000000..8c48f73 --- /dev/null +++ b/fediseer/apis/v1/report.py @@ -0,0 +1,56 @@ +from fediseer.apis.v1.base import * +from fediseer import enums + +class Report(Resource): + + get_parser = reqparse.RequestParser() + get_parser.add_argument("Client-Agent", default="unknown:0:unknown", type=str, required=False, help="The client name and version.", location="headers") + get_parser.add_argument("source_domains_csv", required=False, default=None, type=str, help="A csv of source domains for which to filter", location="args") + get_parser.add_argument("target_domains_csv", required=False, default=None, type=str, help="A csv of target domains for which to filter", location="args") + get_parser.add_argument("report_type", required=False, default=None, type=str, help=f"The activity of report to filer {[e.name for e in enums.ReportType]}", location="args") + get_parser.add_argument("report_activity", required=False, default=None, type=str, help=f"The activity of report to filer {[e.name for e in enums.ReportActivity]}", location="args") + + @api.expect(get_parser) + @api.marshal_with(models.response_model_reports, code=200, description='Report', as_list=True) + @api.response(400, 'Validation Error', models.response_model_error) + def get(self,page=1): + '''Retrieve instance information via API Key at 10 results per page + ''' + self.args = self.get_parser.parse_args() + source_domains = None + if self.args.source_domains_csv: + source_domains = self.args.source_domains_csv.split(',') + target_domains = None + if self.args.target_domains_csv: + target_domains = self.args.target_domains_csv.split(',') + report_type = None + if self.args.report_type: + try: + report_type = enums.ReportType[self.args.report_type] + except KeyError as err: + raise e.BadRequest(f"'{self.args.report_type}' is not a valid ReportType") + report_activity = None + if self.args.report_activity: + try: + report_activity = enums.ReportActivity[self.args.report_activity] + except KeyError as err: + raise e.BadRequest(f"'{self.args.report_activity}' is not a valid ReportActivity") + reports = database.get_reports( + source_instances = source_domains, + target_instances = target_domains, + report_type=report_type, + report_activity=report_activity, + page=page, + ) + report_response = [] + for r in reports: + report_response.append( + { + 'source_domain': r.source_domain, + 'target_domain': r.target_domain, + 'report_type': r.report_type.name, + 'report_activity': r.report_activity.name, + 'created': r.created, + } + ) + return report_response,200 diff --git a/fediseer/classes/__init__.py b/fediseer/classes/__init__.py index 38bd68c..2ce2c90 100644 --- a/fediseer/classes/__init__.py +++ b/fediseer/classes/__init__.py @@ -9,6 +9,7 @@ from fediseer.utils import hash_api_key from fediseer.classes.instance import Instance, Guarantee from fediseer.classes.user import User, Claim import fediseer.classes.user +import fediseer.classes.reports with OVERSEER.app_context(): diff --git a/fediseer/classes/reports.py b/fediseer/classes/reports.py new file mode 100644 index 0000000..c06b09a --- /dev/null +++ b/fediseer/classes/reports.py @@ -0,0 +1,16 @@ +from datetime import datetime +from sqlalchemy import Enum + +from fediseer.flask import db, SQLITE_MODE +from fediseer import enums + +class Report(db.Model): + __tablename__ = "reports" + + id = db.Column(db.Integer, primary_key=True) + # We don't do relations as we don't care if the columns are linked + source_domain = db.Column(db.String(255), unique=False, nullable=False, index=True) + target_domain = db.Column(db.String(255), unique=False, nullable=False, index=True) + report_type = db.Column(Enum(enums.ReportType), nullable=False, index=True) + report_activity = db.Column(Enum(enums.ReportActivity), nullable=False, index=True) + created = db.Column(db.DateTime, default=datetime.utcnow, nullable=False) \ No newline at end of file diff --git a/fediseer/consts.py b/fediseer/consts.py index b4a7eb6..d385f12 100644 --- a/fediseer/consts.py +++ b/fediseer/consts.py @@ -1,4 +1,4 @@ -FEDISEER_VERSION = "0.9.1" +FEDISEER_VERSION = "0.10.0" SUPPORTED_SOFTWARE = [ "lemmy", "mastodon", diff --git a/fediseer/database/functions.py b/fediseer/database/functions.py index 5530d91..2b8512a 100644 --- a/fediseer/database/functions.py +++ b/fediseer/database/functions.py @@ -10,6 +10,8 @@ from fediseer.utils import hash_api_key from sqlalchemy.orm import joinedload from fediseer.classes.instance import Instance, Endorsement, Guarantee, RejectionRecord, Censure from fediseer.classes.user import Claim, User +from fediseer.classes.reports import Report +from fediseer import enums def get_all_instances(min_endorsements = 0, min_guarantors = 1): query = db.session.query( @@ -300,3 +302,25 @@ def get_instances_by_ids(instance_ids): Instance.id.in_(instance_ids) ) return query + +def get_reports( + source_instances: list = None, + target_instances: list = None, + report_type: enums.ReportType = None, + report_activity: enums.ReportActivity = None, + page: int = 1, + ): + query = Report.query + if source_instances is not None and len(source_instances) > 0: + query = query.filter(Report.source_domain.in_(source_instances) + ) + if target_instances is not None and len(target_instances) > 0: + query = query.filter(Report.target_domain.in_(target_instances) + ) + if report_type is not None: + query = query.filter(Report.report_type == report_type.name + ) + if report_activity is not None: + query = query.filter(Report.report_activity == report_activity.name + ) + return query.order_by(Report.created.desc()).offset(10 * (page - 1)).limit(10).all() diff --git a/fediseer/enums.py b/fediseer/enums.py new file mode 100644 index 0000000..4b7f003 --- /dev/null +++ b/fediseer/enums.py @@ -0,0 +1,12 @@ +import enum + +class ReportType(enum.Enum): + GUARANTEE = 0 + ENDORSEMENT = 1 + CENSURE = 2 + RESTRICTION = 3 + +class ReportActivity(enum.Enum): + ADDED = 0 + DELETED = 1 + MODIFIED = 2