src/dnsbl/i18n.py \
src/dnsbl/lists.py \
src/dnsbl/logger.py \
+ src/dnsbl/reports.py \
src/dnsbl/sources.py \
src/dnsbl/util.py
-- PostgreSQL database dump
--
-\restrict b3umosWsIJkpOpBVplcLzTd5va09HE84xdwzTROCiAHoMRKnogpbKgIksBp6ahf
+\restrict delNzbfbaFwfjowYSA0Hv8h5YKH5ddoC9grkkzzBvFQDPYVsiqb0QZFyQzEzwfL
-- Dumped from database version 17.6 (Debian 17.6-0+deb13u1)
-- Dumped by pg_dump version 17.6 (Debian 17.6-0+deb13u1)
ALTER SEQUENCE public.nameservers_id_seq OWNED BY public.nameservers.id;
+--
+-- Name: reports; Type: TABLE; Schema: public; Owner: -
+--
+
+CREATE TABLE public.reports (
+ id integer NOT NULL,
+ list_id integer NOT NULL,
+ name text NOT NULL,
+ reported_at timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ reported_by text NOT NULL,
+ closed_at timestamp with time zone,
+ closed_by text,
+ comment text DEFAULT ''::text NOT NULL,
+ block boolean DEFAULT true,
+ accepted boolean DEFAULT false
+);
+
+
+--
+-- Name: reports_id_seq; Type: SEQUENCE; Schema: public; Owner: -
+--
+
+CREATE SEQUENCE public.reports_id_seq
+ AS integer
+ START WITH 1
+ INCREMENT BY 1
+ NO MINVALUE
+ NO MAXVALUE
+ CACHE 1;
+
+
+--
+-- Name: reports_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
+--
+
+ALTER SEQUENCE public.reports_id_seq OWNED BY public.reports.id;
+
+
--
-- Name: source_domains; Type: TABLE; Schema: public; Owner: -
--
ALTER TABLE ONLY public.nameservers ALTER COLUMN id SET DEFAULT nextval('public.nameservers_id_seq'::regclass);
+--
+-- Name: reports id; Type: DEFAULT; Schema: public; Owner: -
+--
+
+ALTER TABLE ONLY public.reports ALTER COLUMN id SET DEFAULT nextval('public.reports_id_seq'::regclass);
+
+
--
-- Name: source_domains id; Type: DEFAULT; Schema: public; Owner: -
--
ADD CONSTRAINT nameservers_pkey PRIMARY KEY (id);
+--
+-- Name: reports reports_pkey; Type: CONSTRAINT; Schema: public; Owner: -
+--
+
+ALTER TABLE ONLY public.reports
+ ADD CONSTRAINT reports_pkey PRIMARY KEY (id);
+
+
--
-- Name: source_domains source_domains_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--
CREATE UNIQUE INDEX lists_unique ON public.lists USING btree (slug) WHERE (deleted_at IS NULL);
+--
+-- Name: reports_open; Type: INDEX; Schema: public; Owner: -
+--
+
+CREATE INDEX reports_open ON public.reports USING btree (name) WHERE (closed_at IS NULL);
+
+
--
-- Name: source_domains_unique; Type: INDEX; Schema: public; Owner: -
--
CREATE UNIQUE INDEX sources_unique ON public.sources USING btree (list_id, url) WHERE (deleted_at IS NULL);
+--
+-- Name: reports reports_list_id; Type: FK CONSTRAINT; Schema: public; Owner: -
+--
+
+ALTER TABLE ONLY public.reports
+ ADD CONSTRAINT reports_list_id FOREIGN KEY (list_id) REFERENCES public.lists(id);
+
+
--
-- Name: source_domains source_domains_source_id; Type: FK CONSTRAINT; Schema: public; Owner: -
--
-- PostgreSQL database dump complete
--
-\unrestrict b3umosWsIJkpOpBVplcLzTd5va09HE84xdwzTROCiAHoMRKnogpbKgIksBp6ahf
+\unrestrict delNzbfbaFwfjowYSA0Hv8h5YKH5ddoC9grkkzzBvFQDPYVsiqb0QZFyQzEzwfL
from . import auth
from . import database
from . import lists
+from . import reports
from . import sources
class Backend(object):
def lists(self):
return lists.Lists(self)
+ @functools.cached_property
+ def reports(self):
+ return reports.Reports(self)
+
@functools.cached_property
def sources(self):
return sources.Sources(self)
###############################################################################
import fastapi
+import pydantic
import typing
from .. import lists
+from .. import reports
from .. import sources
# Import the main app
+from . import require_api_key
from . import app
from . import backend
def get_list_sources(list = fastapi.Depends(get_list_from_path)) -> typing.List[sources.Source]:
return list.sources
+class CreateReport(pydantic.BaseModel):
+ # Domain
+ name : str
+
+ # Reported By
+ reported_by : str
+
+ # Comment
+ comment : str = ""
+
+ # Block?
+ block : bool = True
+
+
+@router.post("/{list}/reports")
+def list_report(
+ report: CreateReport,
+ auth = fastapi.Depends(require_api_key),
+ list = fastapi.Depends(get_list_from_path),
+) -> reports.Report:
+ # Create a new report
+ with backend.db:
+ return list.report(
+ name = report.name,
+ reported_by = report.reported_by,
+ comment = report.comment,
+ block = report.block,
+ )
+
# Include our endpoints
app.include_router(router)
# Run the export
exporter(f, **kwargs)
+
+ # Reports
+ reports : typing.List["Report"] = sqlmodel.Relationship(back_populates="list")
+
+ # Report!
+
+ def report(self, **kwargs):
+ """
+ Creates a new report for this list
+ """
+ return self.backend.reports.create(list=self, **kwargs)
--- /dev/null
+###############################################################################
+# #
+# dnsbl - A DNS Blocklist Compositor For IPFire #
+# Copyright (C) 2025 IPFire Development Team #
+# #
+# This program is free software: you can redistribute it and/or modify #
+# it under the terms of the GNU General Public License as published by #
+# the Free Software Foundation, either version 3 of the License, or #
+# (at your option) any later version. #
+# #
+# This program is distributed in the hope that it will be useful, #
+# but WITHOUT ANY WARRANTY; without even the implied warranty of #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
+# GNU General Public License for more details. #
+# #
+# You should have received a copy of the GNU General Public License #
+# along with this program. If not, see <http://www.gnu.org/licenses/>. #
+# #
+###############################################################################
+
+import datetime
+import sqlmodel
+
+from . import database
+
+class Reports(object):
+ def __init__(self, backend):
+ self.backend = backend
+
+ def __iter__(self):
+ stmt = (
+ sqlmodel
+ .select(
+ Report,
+ )
+ .where(
+ Report.closed_at == None,
+ )
+ .order_by(
+ Report.created_at,
+ )
+ )
+
+ return self.backend.db.fetch(stmt)
+
+ def create(self, **kwargs):
+ """
+ Creates a new report
+ """
+ report = self.backend.db.insert(
+ Report, **kwargs,
+ )
+
+ return report
+
+
+class Report(sqlmodel.SQLModel, database.BackendMixin, table=True):
+ __tablename__ = "reports"
+
+ # ID
+ id : int = sqlmodel.Field(primary_key=True)
+
+ # List ID
+ list_id : int = sqlmodel.Field(foreign_key="lists.id", exclude=True)
+
+ # List
+ list : "List" = sqlmodel.Relationship(back_populates="reports")
+
+ # Name
+ name : str
+
+ # Reported At
+ reported_at : datetime.datetime = sqlmodel.Field(
+ sa_column_kwargs = {"server_default" : sqlmodel.text("CURRENT_TIMESTAMP")}
+ )
+
+ # Reported By
+ reported_by : str
+
+ # Closed At
+ closed_at : datetime.datetime | None
+
+ # Closed By
+ closed_by : str | None
+
+ # Comment
+ comment : str = ""
+
+ # Block?
+ block : bool = True
+
+ # Accepted?
+ accepted : bool = False
+
+ # Close!
+
+ def close(self):
+ pass # XXX TODO