~liberachat/beryllia

85e6639bd0c98cafc5dfac576e93a4ebe9091d19 — Jess Porter a month ago bb37c81
record account freezes, with freeze reason %tags (#56)

* record account freezes, with freeze reason %tags

* change table name from 'freeze' to 'account_freeze'
M beryllia/database/__init__.py => beryllia/database/__init__.py +4 -0
@@ 13,6 13,8 @@ from .preference import PreferenceTable

from .registration import RegistrationTable
from .email_resolve import EmailResolveTable
from .account_freeze import AccountFreezeTable
from .freeze_tag import FreezeTagTable

from ..normalise import SearchNormaliser



@@ 32,6 34,8 @@ class Database(object):
        self.preference = PreferenceTable(pool, normaliser)
        self.registration = RegistrationTable(pool, normaliser)
        self.email_resolve = EmailResolveTable(pool, normaliser)
        self.account_freeze = AccountFreezeTable(pool, normaliser)
        self.freeze_tag = FreezeTagTable(pool, normaliser)

    @staticmethod
    async def connect(

A beryllia/database/account_freeze.py => beryllia/database/account_freeze.py +14 -0
@@ 0,0 1,14 @@
from .common import Table


class AccountFreezeTable(Table):
    async def add(self, account: str, soper: str, reason: str) -> int:

        query = """
            INSERT INTO account_freeze (account, soper, reason, ts)
            VALUES ($1, $2, $3, NOW()::TIMESTAMP)
            RETURNING id
        """

        async with self.pool.acquire() as conn:
            return await conn.fetchval(query, account, soper, reason)

A beryllia/database/freeze_tag.py => beryllia/database/freeze_tag.py +30 -0
@@ 0,0 1,30 @@
from .common import Table
from ..normalise import SearchType


class FreezeTagTable(Table):
    async def add(self, freeze_id: int, tag: str, soper: str) -> None:

        query = """
            INSERT INTO freeze_tag (freeze_id, tag, search_tag, soper, ts)
            VALUES ($1, $2, $3, $4, NOW()::TIMESTAMP)
        """

        async with self.pool.acquire() as conn:
            await conn.execute(
                query, freeze_id, tag, str(self.to_search(tag, SearchType.TAG)), soper
            )

    async def exists(self, freeze_id: int, tag: str) -> bool:
        query = """
            SELECT 1 FROM freeze_tag
            WHERE freeze_id = $1
            AND search_tag = $2
        """

        async with self.pool.acquire() as conn:
            return bool(
                await conn.fetchval(
                    query, freeze_id, str(self.to_search(tag, SearchType.TAG))
                )
            )

M beryllia/parse/common.py => beryllia/parse/common.py +3 -0
@@ 1,5 1,8 @@
from re import compile as re_compile
from irctokens import Line

RE_EMBEDDEDTAG = re_compile(r"%(\S+)")


class IRCParser:
    async def handle(self, line: Line) -> None:

M beryllia/parse/nickserv.py => beryllia/parse/nickserv.py +25 -1
@@ 3,7 3,7 @@ from typing import Any, Awaitable, Callable, Dict, List, Optional

from irctokens import Line

from .common import IRCParser
from .common import IRCParser, RE_EMBEDDEDTAG
from ..database import Database
from ..util import recursive_mx_resolve



@@ 124,3 124,27 @@ class NickServParser(IRCParser):

        registration_id = self._registration_ids[account]
        await self._database.registration.verify(registration_id)

    @_handler("FREEZE:ON")
    async def _handle_FREEZE_ON(
        self, soper: str, soper_account: Optional[str], args: str
    ) -> None:

        match = re_search(r"(?P<account>\S+) \(reason: (?P<reason>.*)\)$", args)
        if match is None:
            return

        soper = soper_account or soper

        account = match.group("account")
        reason = match.group("reason")

        freeze_id = await self._database.account_freeze.add(account, soper, reason)

        tags = list(RE_EMBEDDEDTAG.finditer(reason))
        for tag_match in tags:
            tag = tag_match.group(1)
            if await self._database.freeze_tag.exists(freeze_id, tag):
                continue

            await self._database.freeze_tag.add(freeze_id, tag, soper)

M beryllia/parse/snote.py => beryllia/parse/snote.py +2 -4
@@ 16,15 16,13 @@ from typing import (

from irctokens import Line

from .common import IRCParser
from .common import IRCParser, RE_EMBEDDEDTAG
from ..database import Database
from ..database.cliconn import Cliconn

_TYPE_HANDLER = Callable[[Any, str, Match], Awaitable[None]]
_HANDLERS: List[Tuple[Pattern, _TYPE_HANDLER]] = []

RE_KLINETAG = re_compile(r"%(\S+)")


def _handler(pattern: str) -> Callable[[_TYPE_HANDLER], _TYPE_HANDLER]:
    def _inner(func: _TYPE_HANDLER) -> _TYPE_HANDLER:


@@ 269,7 267,7 @@ class SnoteParser(IRCParser):
        # TODO: just pass a KLine object to _kline_new
        await self._kline_new(kline_id)

        tags = list(RE_KLINETAG.finditer(reason))
        tags = list(RE_EMBEDDEDTAG.finditer(reason))
        for tag_match in tags:
            tag = tag_match.group(1)
            if await self._database.kline_tag.exists(kline_id, tag):

M make-database.sql => make-database.sql +17 -0
@@ 156,6 156,23 @@ CREATE TABLE email_resolve (
    record           VARCHAR(256)  NOT NULL
);

CREATE TABLE account_freeze (
    id       SERIAL        PRIMARY KEY,
    account  VARCHAR(16)   NOT NULL,
    soper    VARCHAR(16)   NOT NULL,
    reason   VARCHAR(256)  NOT NULL,
    ts       TIMESTAMP     NOT NULL
);

CREATE TABLE freeze_tag (
    freeze_id   INTEGER      NOT NULL  REFERENCES account_freeze (id)  ON DELETE CASCADE,
    tag         VARCHAR(32)  NOT NULL,
    search_tag  VARCHAR(32)  NOT NULL,
    soper       VARCHAR(16)  NOT NULL,
    ts          TIMESTAMP    NOT NULL,
    PRIMARY KEY (freeze_id, search_tag)
);

CREATE TABLE statsp (
    oper VARCHAR(16) NOT NULL,
    mask VARCHAR(92) NOT NULL,