microblog.pub/tasks.py

356 lines
8.4 KiB
Python
Raw Normal View History

2022-07-14 08:44:04 +02:00
import asyncio
2022-06-27 20:55:44 +02:00
import io
import shutil
2022-06-27 20:55:44 +02:00
import tarfile
2022-07-18 20:44:55 +02:00
from contextlib import contextmanager
2022-06-27 20:55:44 +02:00
from pathlib import Path
2022-07-18 20:44:55 +02:00
from typing import Generator
2022-06-22 20:11:22 +02:00
from typing import Optional
2022-06-27 20:55:44 +02:00
import httpx
2022-06-22 20:11:22 +02:00
from invoke import Context # type: ignore
from invoke import run # type: ignore
from invoke import task # type: ignore
@task
def generate_db_migration(ctx, message):
# type: (Context, str) -> None
2022-07-18 20:44:55 +02:00
run(f'alembic revision --autogenerate -m "{message}"', echo=True)
2022-06-22 20:11:22 +02:00
@task
def migrate_db(ctx):
# type: (Context) -> None
2022-07-18 20:44:55 +02:00
run("alembic upgrade head", echo=True)
2022-06-22 20:11:22 +02:00
@task
def autoformat(ctx):
# type: (Context) -> None
run("black .", echo=True)
run("isort -sl .", echo=True)
@task
def lint(ctx):
# type: (Context) -> None
run("black --check .", echo=True)
run("isort -sl --check-only .", echo=True)
run("flake8 .", echo=True)
run("mypy .", echo=True)
@task
def compile_scss(ctx, watch=False):
# type: (Context, bool) -> None
2022-07-22 08:46:14 +02:00
from app.utils.favicon import build_favicon
favicon_file = Path("data/favicon.ico")
if not favicon_file.exists():
build_favicon()
else:
shutil.copy2(favicon_file, "app/static/favicon.ico")
2022-07-15 20:01:55 +02:00
theme_file = Path("data/_theme.scss")
if not theme_file.exists():
theme_file.write_text("// override vars for theming here")
2022-07-04 20:49:23 +02:00
2022-06-22 20:11:22 +02:00
if watch:
2022-07-18 20:44:55 +02:00
run("boussole watch", echo=True)
2022-06-22 20:11:22 +02:00
else:
2022-07-18 20:44:55 +02:00
run("boussole compile", echo=True)
2022-06-22 20:11:22 +02:00
@task
def uvicorn(ctx):
# type: (Context) -> None
2022-07-18 20:44:55 +02:00
run("uvicorn app.main:app --no-server-header", pty=True, echo=True)
2022-06-22 20:11:22 +02:00
@task
def process_outgoing_activities(ctx):
# type: (Context) -> None
2022-06-24 11:33:05 +02:00
from app.outgoing_activities import loop
asyncio.run(loop())
2022-06-22 20:11:22 +02:00
2022-07-14 08:44:04 +02:00
@task
def process_incoming_activities(ctx):
# type: (Context) -> None
from app.incoming_activities import loop
asyncio.run(loop())
2022-06-22 20:11:22 +02:00
@task
def tests(ctx, k=None):
# type: (Context, Optional[str]) -> None
pytest_args = " -vvv"
if k:
pytest_args += f" -k {k}"
run(
f"MICROBLOGPUB_CONFIG_FILE=tests.toml pytest tests{pytest_args}",
pty=True,
echo=True,
)
2022-06-27 20:55:44 +02:00
2022-07-04 19:02:06 +02:00
@task
def generate_requirements_txt(ctx, where="requirements.txt"):
# type: (Context, str) -> None
run(
f"poetry export -f requirements.txt --without-hashes > {where}",
pty=True,
echo=True,
)
@task
def build_docs(ctx):
# type: (Context) -> None
2022-07-18 20:44:55 +02:00
with embed_version():
run("PYTHONPATH=. python scripts/build_docs.py", pty=True, echo=True)
2022-07-04 19:02:06 +02:00
2022-06-27 20:55:44 +02:00
@task
def download_twemoji(ctx):
# type: (Context) -> None
resp = httpx.get(
"https://github.com/twitter/twemoji/archive/refs/tags/v14.0.2.tar.gz",
follow_redirects=True,
)
resp.raise_for_status()
tf = tarfile.open(fileobj=io.BytesIO(resp.content))
members = [
member
for member in tf.getmembers()
if member.name.startswith("twemoji-14.0.2/assets/svg/")
]
for member in members:
emoji_name = Path(member.name).name
with open(f"app/static/twemoji/{emoji_name}", "wb") as f:
f.write(tf.extractfile(member).read()) # type: ignore
2022-07-04 20:49:23 +02:00
2022-08-09 22:29:01 +02:00
@task(download_twemoji, compile_scss)
2022-07-05 09:15:45 +02:00
def configuration_wizard(ctx):
2022-07-04 20:49:23 +02:00
# type: (Context) -> None
2022-08-09 22:29:01 +02:00
run("MICROBLOGPUB_CONFIG_FILE=tests.toml alembic upgrade head", echo=True)
run(
"MICROBLOGPUB_CONFIG_FILE=tests.toml PYTHONPATH=. python scripts/config_wizard.py", # noqa: E501
pty=True,
echo=True,
)
2022-07-07 21:18:08 +02:00
2022-07-08 12:10:20 +02:00
@task
def install_deps(ctx):
# type: (Context) -> None
run("poetry install", pty=True, echo=True)
2022-08-21 09:39:57 +02:00
@task(pre=[compile_scss], post=[migrate_db])
def update(ctx, update_deps=True):
# type: (Context, bool) -> None
if update_deps:
run("poetry install", pty=True, echo=True)
2022-07-07 21:18:08 +02:00
print("Done")
2022-07-08 21:17:08 +02:00
@task
def stats(ctx):
# type: (Context) -> None
from app.utils.stats import print_stats
print_stats()
2022-07-18 20:44:55 +02:00
@contextmanager
def embed_version() -> Generator[None, None, None]:
2022-08-24 09:02:20 +02:00
from app.utils.version import get_version_commit
2022-07-18 20:44:55 +02:00
version_file = Path("app/_version.py")
version_file.unlink(missing_ok=True)
2022-08-24 09:02:20 +02:00
version_commit = get_version_commit()
version_file.write_text(f'VERSION_COMMIT = "{version_commit}"')
2022-07-18 20:44:55 +02:00
try:
yield
finally:
version_file.unlink()
@task
def build_docker_image(ctx):
# type: (Context) -> None
with embed_version():
run("docker build -t microblogpub/microblogpub .")
@task
def prune_old_data(ctx):
# type: (Context) -> None
from app.prune import run_prune_old_data
asyncio.run(run_prune_old_data())
2022-08-21 15:40:25 +02:00
@task
def webfinger(ctx, account):
# type: (Context, str) -> None
import traceback
from loguru import logger
from app.source import _MENTION_REGEX
from app.webfinger import get_actor_url
logger.disable("app")
if not account.startswith("@"):
account = f"@{account}"
if not _MENTION_REGEX.match(account):
print(f"Invalid acccount {account}")
return
print(f"Resolving {account}")
try:
maybe_actor_url = asyncio.run(get_actor_url(account))
if maybe_actor_url:
print(f"SUCCESS: {maybe_actor_url}")
else:
print(f"ERROR: Failed to resolve {account}")
except Exception as exc:
print(f"ERROR: Failed to resolve {account}")
print("".join(traceback.format_exception(exc)))
2022-09-08 20:57:52 +02:00
@task
def move_to(ctx, moved_to):
# type: (Context, str) -> None
import traceback
from loguru import logger
from app.actor import LOCAL_ACTOR
from app.actor import fetch_actor
from app.boxes import send_move
from app.database import async_session
from app.source import _MENTION_REGEX
from app.webfinger import get_actor_url
logger.disable("app")
if not moved_to.startswith("@"):
moved_to = f"@{moved_to}"
if not _MENTION_REGEX.match(moved_to):
print(f"Invalid acccount {moved_to}")
return
async def _send_move():
print(f"Initiating move to {moved_to}")
async with async_session() as db_session:
try:
moved_to_actor_id = await get_actor_url(moved_to)
except Exception as exc:
print(f"ERROR: Failed to resolve {moved_to}")
print("".join(traceback.format_exception(exc)))
return
if not moved_to_actor_id:
print("ERROR: Failed to resolve {moved_to}")
return
new_actor = await fetch_actor(db_session, moved_to_actor_id)
if LOCAL_ACTOR.ap_id not in new_actor.ap_actor.get("alsoKnownAs", []):
print(
f"{new_actor.handle}/{moved_to_actor_id} is missing "
f"{LOCAL_ACTOR.ap_id} in alsoKnownAs"
)
return
2022-09-11 19:26:41 +02:00
await send_move(db_session, new_actor.ap_id)
2022-09-08 20:57:52 +02:00
print("Done")
asyncio.run(_send_move())
2022-09-11 10:51:08 +02:00
@task
def self_destruct(ctx):
# type: (Context) -> None
from loguru import logger
from app.boxes import send_self_destruct
from app.database import async_session
logger.disable("app")
async def _send_self_destruct():
if input("Initiating self destruct, type yes to confirm: ") != "yes":
print("Aborting")
async with async_session() as db_session:
await send_self_destruct(db_session)
print("Done")
asyncio.run(_send_self_destruct())
2022-08-21 15:40:25 +02:00
@task
def yunohost_config(
ctx,
domain,
username,
name,
summary,
password,
):
# type: (Context, str, str, str, str, str) -> None
from app.utils import yunohost
yunohost.setup_config_file(
domain=domain,
username=username,
name=name,
summary=summary,
password=password,
)
2022-09-15 22:47:36 +02:00
@task
def reset_password(ctx):
# type: (Context) -> None
import bcrypt
from prompt_toolkit import prompt
new_password = bcrypt.hashpw(
prompt("New admin password: ", is_password=True).encode(), bcrypt.gensalt()
).decode()
print()
print("Update data/profile.toml with:")
print(f'admin_password = "{new_password}"')
2022-09-16 17:38:19 +02:00
@task
def check_config(ctx):
# type: (Context) -> None
import sys
import traceback
from loguru import logger
logger.disable("app")
try:
from app import config # noqa: F401
except Exception as exc:
print("Config error, please fix data/profile.toml:\n")
print("".join(traceback.format_exception(exc)))
sys.exit(1)
else:
print("Config is OK")