Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Release 1.0.0 #57

Draft
wants to merge 33 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
ce28866
dev_server: Use new event loop
jareddantis Mar 30, 2024
158e284
chore: Migrate from pip to poetry
jareddantis Mar 30, 2024
efc06b4
[skip ci] chore: Ruff lint and format
jareddantis Mar 30, 2024
7269a42
server: Fix imports
jareddantis Mar 30, 2024
9e54fea
Jockey: Split off scrobble handler
jareddantis Mar 30, 2024
b958cf1
chore: Turn magic values into consts
jareddantis Mar 30, 2024
15d204d
ruff: Turn too-many-branches into TODOs for now
jareddantis Mar 30, 2024
954992d
spotify_client: Include query in NoResultsError
jareddantis Mar 30, 2024
52651bf
.github: Use ruff for lint and format
jareddantis Mar 30, 2024
06ca9a4
.github: Install poetry before setting up cache
jareddantis Mar 30, 2024
f9bd468
chore: Update Dockerfile
jareddantis Mar 30, 2024
f6d7244
Jockey: Log raw BlancoException from scrobbler
jareddantis Mar 30, 2024
0a5233e
Dockerfile: Explicitly declare setuptools as dependency
jareddantis Mar 30, 2024
0470202
Regenerate lockfile
jareddantis Mar 30, 2024
f578d57
.github: Explicitly declare PR job perms
jareddantis Mar 30, 2024
e515062
PlayerCog: Decompose find_lavalink_track
jareddantis Mar 30, 2024
854052b
Move bot files into new root folder
jareddantis Mar 30, 2024
5899fbb
feat: Use mypy
jareddantis Mar 30, 2024
485ef67
chore: Make mypy happy
jareddantis Mar 30, 2024
2d5b0bc
bot: Move server static files out
jareddantis Mar 30, 2024
788b021
poetry: Source now lives in bot folder
jareddantis Mar 30, 2024
a8499d8
Makefile: Add targets for local Docker img
jareddantis Mar 30, 2024
2d22b34
.github: Just invoke pre-commit directly
jareddantis Mar 30, 2024
2e39e57
Downgrade back to Python 3.11
jareddantis Mar 31, 2024
2265f27
Dockerfile: Use Tailwind CLI for faster build
jareddantis Mar 31, 2024
327620c
Dockerfile: Fix Tailwind CLI link for amd64
jareddantis Mar 31, 2024
664221f
Cleanup
jareddantis Mar 31, 2024
4bb0c2c
bot: Start implementing new FastAPI-based server
jareddantis Mar 31, 2024
33fe647
bot: Fix bump merge
jareddantis Apr 14, 2024
5dee52d
precommit: Update repos
jareddantis Apr 14, 2024
589a942
api: Implement the rest of the routes
jareddantis Apr 14, 2024
0172067
spotify_client: Fix format string
jareddantis Apr 14, 2024
ba67eac
bot: Remove old aiohttp server
jareddantis Apr 14, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 14 additions & 9 deletions .github/workflows/pull-request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,29 @@ on:
branches:
- 'main'

permissions:
contents: read
packages: write

jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Install poetry
run: pipx install poetry==1.8.2
- uses: actions/setup-python@v4
with:
python-version: 3.11
cache: 'pip'
python-version: 3.12
cache: 'poetry'
cache-dependency-path: poetry.lock
- name: Install dependencies
run: |
python -m pip install --upgrade pip wheel
pip install -r requirements.txt
- name: Install pylint
run: pip install pylint
- name: Run pylint
poetry env use 3.12
poetry install
- name: Run precommit hooks
run: |
pylint --rcfile=.pylintrc --fail-under=9.0 $(git ls-files '*.py')
poetry run pre-commit run --all-files
build:
uses: ./.github/workflows/build.yml

Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ jobs:
prerelease: false
- name: Prune untagged images
uses: actions/delete-package-versions@v4
with:
with:
package-name: 'blanco-bot'
package-type: 'container'
min-versions-to-keep: 3
Expand Down
26 changes: 26 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.6.0
hooks:
- id: double-quote-string-fixer
- id: end-of-file-fixer
- id: trailing-whitespace
- id: no-commit-to-branch
args: ['--branch', 'main']
- id: mixed-line-ending
- id: check-merge-conflict
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.3.7
hooks:
- id: ruff
args: [ --fix ]
- id: ruff-format
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.9.0
hooks:
- id: mypy
args: ['--config-file=mypy.ini', '--check-untyped-defs']
additional_dependencies:
- types-pyyaml==6.0.1
- types-redis==4.6.0.20240311
- types-requests==2.31.0
2 changes: 0 additions & 2 deletions .pylintrc

This file was deleted.

2 changes: 1 addition & 1 deletion .vscode/.gitignore
Original file line number Diff line number Diff line change
@@ -1 +1 @@
launch.json
launch.json
2 changes: 1 addition & 1 deletion .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,4 @@
}
}
]
}
}
8 changes: 5 additions & 3 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@
"files.exclude": {
"**/__pycache__": true
},
"editor.tabSize": 4,
"editor.tabSize": 2,
"css.lint.unknownAtRules": "ignore",
"pylint.args": [
"--disable=too-many-instance-attributes",
"--disable=too-many-locals",
"--disable=too-few-public-methods",
"--disable=too-many-return-statements",
"--disable=too-many-branches"
]
}
],
"editor.formatOnSave": true,
"editor.defaultFormatter": "charliermarsh.ruff"
}
56 changes: 32 additions & 24 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,46 +1,54 @@
ARG RELEASE="0.0.0-unknown"


FROM node:lts-alpine AS tailwind
FROM --platform=$BUILDPLATFORM python:3.11 AS dependencies
ARG TARGETARCH

# Compile Tailwind CSS
RUN mkdir -p /opt/build
COPY tailwind.config.js /opt/build/
COPY server/ /opt/build/server
WORKDIR /opt/build
RUN npm install -D tailwindcss && \
npx tailwindcss -i ./server/static/css/base.css \
-o ./server/static/css/main.css --minify
# Install build-essential for building Python packages
RUN apt-get update && apt-get install -y build-essential curl

# Install Poetry
RUN pip install poetry==1.8.2
ENV POETRY_NO_INTERACTION=1 \
POETRY_VIRTUALENVS_IN_PROJECT=1 \
POETRY_VIRTUALENVS_CREATE=1 \
POETRY_CACHE_DIR=/tmp/poetry_cache

FROM python:3.11 AS dependencies
# Copy files
WORKDIR /app
COPY pyproject.toml poetry.lock tailwind.config.js dashboard/static/css/base.css ./

# Install build-essential for building Python packages
RUN apt-get update && apt-get install -y build-essential
# Install dependencies
RUN --mount=type=cache,target=$POETRY_CACHE_DIR poetry install --without dev

# Install pip requirements under virtualenv
RUN python -m venv /opt/venv
ENV PATH="/opt/venv/bin:${PATH}"
COPY requirements.txt .
RUN pip install --upgrade pip wheel && pip install -r requirements.txt
# Compile Tailwind CSS
RUN echo "Downloading Tailwind CLI for ${TARGETARCH}" && \
if [ "${TARGETARCH}" = "amd64" ]; then \
curl -sL https://github.com/tailwindlabs/tailwindcss/releases/latest/download/tailwindcss-linux-x64 -o ./tailwindcss; \
else \
curl -sL https://github.com/tailwindlabs/tailwindcss/releases/latest/download/tailwindcss-linux-${TARGETARCH} -o ./tailwindcss; \
fi && \
chmod +x ./tailwindcss && \
./tailwindcss -i ./base.css -o ./main.css --minify


FROM python:3.11-slim AS main
ARG RELEASE
COPY --from=dependencies /opt/venv /opt/venv
LABEL maintainer="Jared Dantis <[email protected]>"

# Copy bot files
COPY . /opt/app
COPY --from=tailwind /opt/build/server/static/css/main.css /opt/app/server/static/css/main.css
COPY --from=dependencies /app/.venv /opt/venv
COPY --from=dependencies /app/main.css /opt/app/dashboard/static/css/main.css
WORKDIR /opt/app

# Set release
RUN sed -i "s/0.0.0-unknown/${RELEASE}/" utils/constants.py
RUN sed -i "s/0.0.0-unknown/${RELEASE}/" bot/utils/constants.py

# Run bot
ENV PATH="/opt/venv/bin:${PATH}"
ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1
ENV PATH="/opt/venv/bin:${PATH}" \
PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1
EXPOSE 8080
CMD ["python3", "main.py"]
ENTRYPOINT ["python"]
CMD ["-m", "bot.main"]
29 changes: 29 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
MAKEFLAGS += --jobs=2
.PHONY: install dev
all: install dev-frontend dev precommit image dev-image

install:
poetry env use 3.11
poetry install
poetry run pre-commit install

dev-frontend: config.yml blanco.db
poetry run python -m bot.dev_server

dev-backend: config.yml blanco.db
poetry run python -m bot.api.main

dev: config.yml blanco.db
poetry run python -m bot.main

precommit:
poetry run pre-commit run --all-files

image:
docker buildx build -t blanco-bot .

dev-image: config.yml blanco.db image
docker run --rm -it \
-v $(PWD):/opt/app \
-p 8080:8080 \
blanco-bot
File renamed without changes.
33 changes: 33 additions & 0 deletions bot/api/depends/database.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
from typing import TYPE_CHECKING

from fastapi import HTTPException, Request
from starlette.status import HTTP_500_INTERNAL_SERVER_ERROR

if TYPE_CHECKING:
from bot.database import Database


def database_dependency(request: Request) -> 'Database':
"""
FastAPI dependency to get the database object.

Args:
request (web.Request): The request.

Returns:
Database: The database object.
"""

state = request.app.state
if not hasattr(state, 'database'):
raise HTTPException(
status_code=HTTP_500_INTERNAL_SERVER_ERROR, detail='No database connection'
)

database: 'Database' = state.database
if database is None:
raise HTTPException(
status_code=HTTP_500_INTERNAL_SERVER_ERROR, detail='No database connection'
)

return database
48 changes: 48 additions & 0 deletions bot/api/depends/session.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
from typing import TYPE_CHECKING

from fastapi import HTTPException, Request
from starlette.status import HTTP_401_UNAUTHORIZED

if TYPE_CHECKING:
from bot.api.models.session import Session
from bot.api.utils.session import SessionManager

EXPECTED_AUTH_SCHEME = 'Bearer'
EXPECTED_AUTH_PARTS = 2


def session_dependency(request: Request) -> 'Session':
"""
FastAPI dependency to get the requesting user's session object.

Args:
request (web.Request): The request.

Returns:
Session: The session object for the current Discord user.
"""

authorization = request.headers.get('Authorization')
if authorization is None:
raise HTTPException(
status_code=HTTP_401_UNAUTHORIZED, detail='No authorization header'
)

parts = authorization.split()
if len(parts) != EXPECTED_AUTH_PARTS:
raise HTTPException(
status_code=HTTP_401_UNAUTHORIZED, detail='Invalid authorization header'
)

scheme, token = parts
if scheme != EXPECTED_AUTH_SCHEME:
raise HTTPException(
status_code=HTTP_401_UNAUTHORIZED, detail='Invalid authorization scheme'
)

session_manager: 'SessionManager' = request.app.state.session_manager
session = session_manager.decode_session(token)
if session is None:
raise HTTPException(status_code=HTTP_401_UNAUTHORIZED, detail='Invalid session')

return session
30 changes: 30 additions & 0 deletions bot/api/depends/user.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
from typing import TYPE_CHECKING, Optional

from fastapi import Depends, HTTPException
from starlette.status import HTTP_401_UNAUTHORIZED

from .database import database_dependency
from .session import session_dependency

if TYPE_CHECKING:
from bot.api.models.session import Session
from bot.database import Database
from bot.models.oauth import OAuth


def user_dependency(
db: 'Database' = Depends(database_dependency),
session: 'Session' = Depends(session_dependency),
) -> 'OAuth':
"""
FastAPI dependency to get the requesting user's info.

Returns:
OAuth: The info for the current Discord user.
"""

user: Optional['OAuth'] = db.get_oauth('discord', session.user_id)
if user is None:
raise HTTPException(status_code=HTTP_401_UNAUTHORIZED, detail='User not found')

return user
17 changes: 17 additions & 0 deletions bot/api/extension.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
"""
Nextcord extension that runs the API server for the bot
"""

from typing import TYPE_CHECKING

from .main import run_app

if TYPE_CHECKING:
from bot.utils.blanco import BlancoBot


def setup(bot: 'BlancoBot'):
"""
Run the API server within the bot's existing event loop.
"""
run_app(bot.loop, bot.database)
Loading
Loading