This commit is contained in:
parent
7eb261a945
commit
3c1a44c4ee
|
@ -17,10 +17,11 @@ import pgsql
|
||||||
|
|
||||||
|
|
||||||
class Config:
|
class Config:
|
||||||
def __init__(self):
|
|
||||||
"""
|
"""
|
||||||
Get config from environment variables
|
Get config from environment variables
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
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):
|
||||||
|
@ -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)
|
||||||
|
|
Loading…
Reference in a new issue