]> git.ipfire.org Git - dbl.git/commitdiff
reports: Send am email to moderators if there are pending reports
authorMichael Tremer <michael.tremer@ipfire.org>
Fri, 9 Jan 2026 15:59:33 +0000 (15:59 +0000)
committerMichael Tremer <michael.tremer@ipfire.org>
Fri, 9 Jan 2026 15:59:33 +0000 (15:59 +0000)
Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
src/database.sql
src/dnsbl/lists.py
src/dnsbl/reports.py
src/scripts/dnsbl.in

index df509927538dc82fe9ecba23d101f875a1b4b02d..4dea3013859fa2e1c10bcc7e95a5fc4b6587ff84 100644 (file)
@@ -2,7 +2,7 @@
 -- PostgreSQL database dump
 --
 
-\restrict 80aOA5yV31bCsEeXZQpelpLcW1cJfgnz27c1dg1p4ydlMSFyDUMztrDG4WdSfbB
+\restrict 8GAoi0Gc8SzP92GNEI0astiMcEG2ZIy6XPaWB8DUlAso6LnUJFKe8gAEh9fb47M
 
 -- Dumped from database version 17.6 (Debian 17.6-0+deb13u1)
 -- Dumped by pg_dump version 17.6 (Debian 17.6-0+deb13u1)
@@ -281,7 +281,8 @@ CREATE TABLE public.reports (
     closed_by text,
     comment text DEFAULT ''::text NOT NULL,
     block boolean DEFAULT true,
-    accepted boolean DEFAULT false
+    accepted boolean DEFAULT false,
+    notified_at timestamp with time zone
 );
 
 
@@ -641,5 +642,5 @@ ALTER TABLE ONLY public.sources
 -- PostgreSQL database dump complete
 --
 
-\unrestrict 80aOA5yV31bCsEeXZQpelpLcW1cJfgnz27c1dg1p4ydlMSFyDUMztrDG4WdSfbB
+\unrestrict 8GAoi0Gc8SzP92GNEI0astiMcEG2ZIy6XPaWB8DUlAso6LnUJFKe8gAEh9fb47M
 
index 6dc4d9c4a55663afc948757a14fcbf9f10601da8..7e5749327179d0e8fe41327d18d03bc455fbd578 100644 (file)
@@ -171,6 +171,18 @@ class List(sqlmodel.SQLModel, database.BackendMixin, table=True):
 
                return hash(self.id)
 
+       def __eq__(self, other):
+               if isinstance(other, self.__class__):
+                       return self.id == other.id
+
+               return NotImplemented
+
+       def __lt__(self, other):
+               if isinstance(other, self.__class__):
+                       return self.name < other.name
+
+               return NotImplemented
+
        # ID
        id : int = sqlmodel.Field(primary_key=True, exclude=True)
 
index ba93e7746da1a86fa253f2034e50a6b7b59b516d..6a4e2adf5906a9ff38f1627e13405061667f1be5 100644 (file)
@@ -18,6 +18,7 @@
 #                                                                             #
 ###############################################################################
 
+import babel.dates
 import datetime
 import logging
 import pydantic
@@ -25,6 +26,7 @@ import sqlmodel
 import uuid
 
 from . import database
+from .i18n import _
 
 # Setup logging
 log = logging.getLogger(__name__)
@@ -75,6 +77,92 @@ class Reports(object):
 
                return report
 
+       def notify(self):
+               """
+                       Notifies moderators about any pending reports
+               """
+               # Fetch all pending reports that have not been notified, yet
+               reports = self.backend.db.fetch(
+                       sqlmodel
+                       .update(
+                               Report,
+                       )
+                       .values(
+                               notified_at = sqlmodel.func.current_timestamp(),
+                       )
+                       .where(
+                               # Reports must be open
+                               Report.closed_at == None,
+
+                               # Reports must not have been notified, yet
+                               Report.notified_at == None,
+                       )
+                       .returning(
+                               Report,
+                       )
+               )
+
+               lists = {}
+
+               # Group reports by list
+               for report in reports:
+                       try:
+                               lists[report.list].append(report)
+                       except KeyError:
+                               lists[report.list] = [report]
+
+               # Nothing to do
+               if not lists:
+                       log.debug("No pending reports, won't send anything")
+                       return
+
+               # Send an email to all moderators
+               for moderator in self.backend.users.moderators:
+                       lines = [
+                               _("Dear moderator,"),
+                               "",
+                               _("The following reports have been filed and are awaiting your review:"),
+                               "",
+                       ]
+
+                       for list in sorted(lists):
+                               # Headline
+                               lines.append("%s" % list)
+
+                               # List all reports
+                               for report in sorted(lists[list]):
+                                       if report.block:
+                                               headline = _("%s should be blocked") % report.name
+                                       else:
+                                               headline = _("%s should be allowed") % report.name
+
+                                       # Add the headline
+                                       lines.append("  * %s" % headline)
+
+                                       # URL
+                                       lines.append("    %s" % report.url)
+
+                                       # Fetch the reporter
+                                       reporter = self.backend.users.get_by_uid(report.reported_by)
+                                       if reporter:
+                                               lines.append("    %s" % (_("Reported by %s") % reporter))
+
+                                       # Report Time
+                                       lines.append("    %s" % babel.dates.format_datetime(report.reported_at))
+
+                                       # Empty line
+                                       lines.append("")
+
+                       # Send the message
+                       moderator.sendmail("\n".join(lines), headers={
+                               "Subject" : _("[IPFire DNSBL] Pending Reports"),
+
+                               # Mark that this email is auto-generated
+                               "Auto-Submitted"           : "auto-generated",
+                               "X-Auto-Response-Suppress" : "OOF, AutoReply",
+                               "Precedence"               : "bulk",
+                       })
+
 
 class Report(sqlmodel.SQLModel, database.BackendMixin, table=True):
        __tablename__ = "reports"
@@ -82,6 +170,19 @@ class Report(sqlmodel.SQLModel, database.BackendMixin, table=True):
        def __str__(self):
                return "%s" % self.id
 
+       def __eq__(self, other):
+               if isinstance(other, self.__class__):
+                       return self.id == other.id
+
+               return NotImplemented
+
+       def __lt__(self, other):
+               if isinstance(other, self.__class__):
+                       return (self.list, self.reported_at, self.name) \
+                               < (other.list, other.reported_at, other.name)
+
+               return NotImplemented
+
        # ID
        id : uuid.UUID = sqlmodel.Field(primary_key=True, default=sqlmodel.func.gen_random_uuid())
 
@@ -163,3 +264,12 @@ class Report(sqlmodel.SQLModel, database.BackendMixin, table=True):
 
                        # Update the list's stats
                        self.list.update_stats()
+
+       # Notified At
+       notified_at: datetime.datetime | None = None
+
+       # URL
+
+       @property
+       def url(self):
+               return "https://www.ipfire.org/dnsbl/reports/%s" % self.id
index f5b996d164684dbab098e6e0700a721dd726794f..28fe050f9c489c437f6a0a04e7c4157c9d3ae771 100644 (file)
@@ -207,6 +207,11 @@ class CLI(object):
                                help=_("Reject the report"))
                close_report.set_defaults(func=self.__close_report)
 
+               # Notify
+               notify = subparsers.add_parser("notify",
+                               help=_("Notifies moderators about any pending reports"))
+               notify.set_defaults(func=self.__notify)
+
                # Parse all arguments
                args = parser.parse_args()
 
@@ -604,6 +609,14 @@ class CLI(object):
                        accept    = args.accept and not args.reject,
                )
 
+       # Notify
+
+       def __notify(self, backend, args):
+               """
+                       Notifies moderators about any pending reports
+               """
+               backend.reports.notify()
+
 
 def main():
        c = CLI()