Improve docs
All checks were successful
docker-image / docker (push) Successful in 3m41s

This commit is contained in:
Stefan Bethke 2024-05-19 12:35:42 +02:00
parent 7eb261a945
commit 3c1a44c4ee

View file

@ -17,10 +17,11 @@ import pgsql
class Config: class Config:
"""
Get config from environment variables
"""
def __init__(self): def __init__(self):
"""
Get config from environment variables
"""
self.postgres_hostname = getenv('POSTGRES_HOSTNAME', 'localhost') self.postgres_hostname = getenv('POSTGRES_HOSTNAME', 'localhost')
self.postgres_username = getenv('POSTGRES_USERNAME', 'hedgedoc') self.postgres_username = getenv('POSTGRES_USERNAME', 'hedgedoc')
self.postgres_password = getenv('POSTGRES_PASSWORD', 'geheim') self.postgres_password = getenv('POSTGRES_PASSWORD', 'geheim')
@ -36,6 +37,10 @@ class Config:
class EmailSender: class EmailSender:
"""
Send email message through SMTP
"""
def __init__(self, hostname: str, port: int, username: str, password: str, mail_from: str): def __init__(self, hostname: str, port: int, username: str, password: str, mail_from: str):
self.hostname = hostname self.hostname = hostname
self.port = port self.port = port
@ -44,6 +49,12 @@ class EmailSender:
self.mail_from = mail_from self.mail_from = mail_from
def send(self, message: email.message.Message) -> None: def send(self, message: email.message.Message) -> None:
"""
Using the configured SMTP coordinates, send the message out. The code assumes the submission protocol with
StartTLS enabled, and authentication required.
:param message: to be sent
:return:
"""
smtp_server = smtplib.SMTP(self.hostname, port=self.port) smtp_server = smtplib.SMTP(self.hostname, port=self.port)
context = ssl.create_default_context() context = ssl.create_default_context()
smtp_server.starttls(context=context) smtp_server.starttls(context=context)
@ -51,7 +62,14 @@ class EmailSender:
smtp_server.send_message(message) smtp_server.send_message(message)
def email_from_email_or_profile(row): def email_from_email_or_profile(row) -> str:
"""
Get the email address of the creator from a database row. If the email column is populated, use that, otherwise
try to extract it from the login profile. The profile is a JSON object that has an emails array. We're using the
first address from there.
:param row: database row as a dict with email and profile columns
:return: email address
"""
if row['email'] is not None: if row['email'] is not None:
return row['email'] return row['email']
profile = json.loads(row['profile']) profile = json.loads(row['profile'])
@ -59,6 +77,11 @@ def email_from_email_or_profile(row):
def notes_to_be_expired(cutoff: datetime) -> list[any]: def notes_to_be_expired(cutoff: datetime) -> list[any]:
"""
Get a list of all notes to be expired.
:param cutoff: notes that have last beed updated before this date are designated to be expired.
:return:
"""
notes = [] notes = []
with db.prepare('''SELECT with db.prepare('''SELECT
"Notes"."alias", "Notes"."alias",
@ -129,6 +152,12 @@ def revisions_to_be_expired(cutoff: datetime) -> list[any]:
def check_notes_to_be_expired(age: timedelta, config: Config) -> None: def check_notes_to_be_expired(age: timedelta, config: Config) -> None:
"""
Print a list of notes that will be expired.
:param age: expire notes not updated in this timespan
:param config: configuration parameters used in output
:return:
"""
cutoff = datetime.now(timezone.utc) - age cutoff = datetime.now(timezone.utc) - age
print(f'Notes to be deleted older than {cutoff} ({humanize.naturaldelta(age)}):') print(f'Notes to be deleted older than {cutoff} ({humanize.naturaldelta(age)}):')
for note in notes_to_be_expired(cutoff): for note in notes_to_be_expired(cutoff):
@ -138,6 +167,12 @@ def check_notes_to_be_expired(age: timedelta, config: Config) -> None:
def check_revisions_to_be_expired(age: timedelta, config: Config) -> None: def check_revisions_to_be_expired(age: timedelta, config: Config) -> None:
"""
Print a list of revisions that will be expired.
:param age: expire revisions created before this timespan
:param config: configuration parameters used in output
:return:
"""
cutoff = datetime.now(timezone.utc) - age cutoff = datetime.now(timezone.utc) - age
print(f'Revisions to be deleted older than {cutoff} ({humanize.naturaldelta(age)}):') print(f'Revisions to be deleted older than {cutoff} ({humanize.naturaldelta(age)}):')
notes = {} notes = {}
@ -148,13 +183,21 @@ def check_revisions_to_be_expired(age: timedelta, config: Config) -> None:
notes[row['noteId']].append(row) notes[row['noteId']].append(row)
for id, revisions in notes.items(): for id, revisions in notes.items():
email = email_from_email_or_profile(revisions[0]) email = email_from_email_or_profile(revisions[0])
url = config.url + '/' + (revisions[0]["alias"] if revisions[0]["alias"] is not None else revisions[0]["shortid"]) url = config.url + '/' + (
revisions[0]["alias"] if revisions[0]["alias"] is not None else revisions[0]["shortid"])
print(f' {email} {url}: {revisions[0]["title"]}') print(f' {email} {url}: {revisions[0]["title"]}')
for rev in revisions: for rev in revisions:
print(f' {humanize.naturaldelta(rev["age"])}: {rev["revisionId"]}') print(f' {humanize.naturaldelta(rev["age"])}: {rev["revisionId"]}')
def expire_old_notes(age: timedelta, config: Config, mail: EmailSender) -> None: def expire_old_notes(age: timedelta, config: Config, mail: EmailSender) -> None:
"""
Email old notes to their owners, then delete them.
:param age: expire notes not updated in this timespan
:param config: configuration parameters used in output
:param mail: how to send the mail
:return:
"""
cutoff = datetime.now(timezone.utc) - age cutoff = datetime.now(timezone.utc) - age
with db.prepare('DELETE FROM "Notes" WHERE "id" = $1') as delete_statement: with db.prepare('DELETE FROM "Notes" WHERE "id" = $1') as delete_statement:
for note in notes_to_be_expired(cutoff): for note in notes_to_be_expired(cutoff):
@ -174,7 +217,7 @@ def expire_old_notes(age: timedelta, config: Config, mail: EmailSender) -> None:
The admin team for {config.url} The admin team for {config.url}
''' '''
))) )))
md = MIMEBase('text', "markdown") md = MIMEBase('text', "markdown")
md.add_header('Content-Disposition', f'attachment; filename={note["title"]}') md.add_header('Content-Disposition', f'attachment; filename={note["title"]}')
md.set_payload(note["content"]) md.set_payload(note["content"])
@ -186,6 +229,7 @@ def expire_old_notes(age: timedelta, config: Config, mail: EmailSender) -> None:
except Exception as e: except Exception as e:
print(f'Unable to send email to {note["email"]}: {e}', file=sys.stderr) print(f'Unable to send email to {note["email"]}: {e}', file=sys.stderr)
def expire_old_revisions(age: timedelta) -> None: def expire_old_revisions(age: timedelta) -> None:
""" """
Removes all revision on all notes that have been modified earlier than age. Removes all revision on all notes that have been modified earlier than age.
@ -214,9 +258,11 @@ if __name__ == '__main__':
notes_delta = timedelta(days=args.notes) notes_delta = timedelta(days=args.notes)
config = Config() config = Config()
mail = EmailSender(config.smtp_hostname, config.smtp_port, config.smtp_username, config.smtp_password, config.smtp_from) mail = EmailSender(config.smtp_hostname, config.smtp_port, config.smtp_username, config.smtp_password,
config.smtp_from)
with pgsql.Connection((config.postgres_hostname, config.postgres_port), config.postgres_username, config.postgres_password) as db: with pgsql.Connection((config.postgres_hostname, config.postgres_port), config.postgres_username,
config.postgres_password) as db:
if args.check: if args.check:
check_revisions_to_be_expired(revisions_delta, config) check_revisions_to_be_expired(revisions_delta, config)
check_notes_to_be_expired(notes_delta, config) check_notes_to_be_expired(notes_delta, config)