Skip to content

Commit

Permalink
Backend Localization (#218)
Browse files Browse the repository at this point in the history
* Initial fluent support for backend localization:
 - Adds starlette_context so we can safely have per-request data
 - Adds a middleware to read accept-language and load fluent
 - Adds a `l10n` helper function to localize a given key
 - Adjusts the health check to return a distinct string between locales (we use this for tests...)
 - A couple of string -> fluent conversions

* Localize emails, add tests for those emails, and fix test pathing issues

* Install the package instead of manually installing requirements.

* Remove the `if not subscriber` from a load of routes, because we do that in the dependency. Also re-org the exception to a separate file.

* Swap some commonly used exceptions for a validation exception class, and l10n-ify api

* Refactor exception names and definition flow. And replace a few more in schedule.py

* Add a few more exceptions and finish off schedule.py

* Update the zoom.py exceptions

* Update the health check strings

* Remove `DATABASE_SECRETS` which is used for AWS, and add a default database url for docker.

* 🔨 fix legacy env var

* Revert committing frontend testing stash changes on this pr.

* 🌐 add German backend translation

---------

Co-authored-by: Andreas Müller <[email protected]>
  • Loading branch information
MelissaAutumn and devmount authored Jan 9, 2024
1 parent 7dfff89 commit e3abe71
Show file tree
Hide file tree
Showing 58 changed files with 880 additions and 363 deletions.
5 changes: 2 additions & 3 deletions .github/workflows/backend-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,7 @@ jobs:
run: |
cd ./backend
python -m pip install --upgrade pip
python -m pip install -r requirements.txt
python -m pip install -r requirements-test.txt
python -m pip install .'[test]'
- name: Test with pytest
run: |
cd ./backend/test && python -m pytest
cd ./backend && python -m pytest
25 changes: 13 additions & 12 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
.vscode
__pycache__
.pytest_cache
dist
*.db
*.ini
*.log
*credentials.json
*.pickle
.env
venv
.idea
.vscode
__pycache__
.pytest_cache
dist
*.db
*.ini
*.log
*credentials.json
*.pickle
.env
venv
.idea
*.egg-info
14 changes: 9 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,10 @@ Run application for development with hot reloading backend and frontend:
```bash
cd appointment
pip install -r backend/requirements.txt
pip install .
touch backend/src/appointment.db # when using sqlite
cp backend/.env.example backend/.env # add your own configuration here
uvicorn --factory backend.src.appointment.main:server --host 0.0.0.0 --port 5000
uvicorn --factory appointment.main:server --host 0.0.0.0 --port 5000
```
You can now access the backend at [localhost:5000](http://localhost:5000).
Expand Down Expand Up @@ -74,16 +74,16 @@ Run application for development with hot reloading backend and frontend:
## Testing
To run tests, setup the application manually (you don't need the mysql deps), and then install requirements-test.txt
To run tests, simply install the package in editing mode:
```bash
pip install -r requirements-test.txt
cd backend && pip install -e .
```
After this you can run tests with:
```bash
cd backend/test && python -m pytest
cd backend && python -m pytest
```
## Contributing
Expand Down Expand Up @@ -116,3 +116,7 @@ Commands (from /frontend)
yarn run lint
yarn run lint --fix
```
### Localization
This project uses [Fluent](https://projectfluent.org/) for localization. Files are located in their respective `l10n/<locale>/*.ftl`.
3 changes: 1 addition & 2 deletions backend/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,7 @@ FRONTEND_URL=http://localhost:8080
SHORT_BASE_URL=

# -- DATABASE --
DATABASE_URL=
DATABASE_SECRETS=
DATABASE_URL="mysql+mysqldb://tba:tba@mysql:3306/appointment"
# Secret phrase for database encryption (e.g. create it by running `openssl rand -hex 32`)
DB_SECRET=

Expand Down
51 changes: 28 additions & 23 deletions backend/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,23 +1,28 @@
FROM python:3.11-buster

RUN mkdir app
WORKDIR /app

ENV PATH="${PATH}:/root/.local/bin"
ENV PYTHONPATH=.

RUN mkdir scripts

COPY requirements.txt .
COPY pyproject.toml .
COPY alembic.ini.example alembic.ini
COPY scripts/dev-entry.sh scripts/dev-entry.sh

# Dev only
COPY .env .

RUN pip install --upgrade pip
RUN pip install .

EXPOSE 5000
CMD ["/bin/sh", "./scripts/dev-entry.sh"]
FROM python:3.11-buster

RUN mkdir app
WORKDIR /app

ENV PATH="${PATH}:/root/.local/bin"
ENV PYTHONPATH=.

RUN mkdir scripts

COPY requirements.txt .
COPY pyproject.toml .
COPY alembic.ini.example alembic.ini
COPY scripts/dev-entry.sh scripts/dev-entry.sh

# Dev only
COPY .env .

RUN pip install --upgrade pip
RUN pip install .'[deploy]'

# Add this hack to line it up with our dev environment.
# I'll buy whoever fixes this a coffee.
RUN mkdir src
RUN ln -s /app/appointment src/appointment

EXPOSE 5000
CMD ["/bin/sh", "./scripts/dev-entry.sh"]
Empty file removed backend/__init__.py
Empty file.
4 changes: 2 additions & 2 deletions backend/alembic.ini.example
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

[alembic]
# path to migration scripts
script_location = src/appointment/migrations
script_location = appointment/migrations

# template used to generate migration file names; The default value is %%(rev)s_%%(slug)s
# Uncomment the line below if you want the files to be prepended with date and time
Expand All @@ -12,7 +12,7 @@ file_template = %%(year)d_%%(month).2d_%%(day).2d_%%(hour).2d%%(minute).2d-%%(re

# sys.path path, will be prepended to sys.path if present.
# defaults to the current working directory.
prepend_sys_path = src/appointment
prepend_sys_path = appointment

# timezone to use when rendering the date within the migration file
# as well as the filename.
Expand Down
4 changes: 2 additions & 2 deletions backend/deploy.dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ COPY scripts/entry.sh scripts/entry.sh
COPY src .

RUN pip install --upgrade pip
RUN pip install .
RUN pip install .'[deploy]'

# install removes the src file and installs the application as /app/appointment
# that's fine, but uhh let's add this hack to line it up with our dev environment.
Expand All @@ -26,4 +26,4 @@ RUN mkdir src
RUN ln -s /app/appointment src/appointment

EXPOSE 5000
CMD ["/bin/sh", "./scripts/entry.sh"]
CMD ["/bin/sh", "./scripts/entry.sh"]
16 changes: 0 additions & 16 deletions backend/google_credentials.json.example

This file was deleted.

17 changes: 15 additions & 2 deletions backend/pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
[project]
name = "appointment_backend"
name = "appointment"
version = "0.2.0"
description = "Backend component to Thunderbird Appointment"
requires-python = ">3.11"
dynamic = ["dependencies"]

[project.scripts]
run-command = "src.appointment.main:cli"
run-command = "appointment.main:cli"

[project.urls]
homepage = "https://appointment.day"
Expand All @@ -17,6 +17,16 @@ cli = [
"ruff",
"black"
]
db = [
"mysqlclient==2.1.1",
"mysql-connector-python==8.0.32",
]
test = [
"Faker==20.1.0",
"httpx==0.25.1",
"pytest==7.4.3",
]
deploy = ['appointment[cli]', 'appointment[db]']

[tool.setuptools.dynamic]
dependencies = { file = ["requirements.txt"] }
Expand Down Expand Up @@ -70,3 +80,6 @@ max-complexity = 10

[tool.black]
line-length = 120

[tool.pytest.ini_options]
pythonpath = "test"
3 changes: 0 additions & 3 deletions backend/requirements-test.txt

This file was deleted.

6 changes: 4 additions & 2 deletions backend/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,23 +1,25 @@
alembic==1.9.3
argon2-cffi==23.1.0
argon2-cffi-bindings==21.2.0
Babel==2.14.0
caldav==1.0.1
cryptography==39.0.1
fastapi-auth0==0.3.2
fastapi==0.104.1
fluent.runtime==0.4.0
fluent.syntax==0.19.0
google-api-python-client==2.85.0
google-auth-httplib2==0.1.0
google-auth-oauthlib==1.0.0
jinja2==3.1.2
icalendar==5.0.4
itsdangerous==2.1.2
mysqlclient==2.1.1
mysql-connector-python==8.0.32
python-dotenv==1.0.0
python-jose==3.3.0
python-multipart==0.0.6
pydantic==2.5.2
sentry-sdk==1.26.0
starlette-context==0.3.6
sqlalchemy-utils==0.39.0
sqlalchemy==1.4.40
uvicorn==0.20.0
Expand Down
3 changes: 2 additions & 1 deletion backend/scripts/dev-entry.sh
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ run-command update-db
python -u -m smtpd -n -c DebuggingServer localhost:8050 &

# Start up real webserver
uvicorn --factory src.appointment.main:server --reload --host 0.0.0.0 --port 5173
uvicorn --factory appointment.main:server --reload --host 0.0.0.0 --port 5173

2 changes: 1 addition & 1 deletion backend/scripts/entry.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@

run-command update-db

uvicorn --factory src.appointment.main:server --host 0.0.0.0 --port 5000
uvicorn --factory appointment.main:server --host 0.0.0.0 --port 5000
6 changes: 3 additions & 3 deletions backend/src/appointment/controller/calendar.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
from ..database import schemas
from ..database.models import CalendarProvider
from ..controller.mailer import Attachment, InvitationMail

from ..l10n import l10n

DATEFMT = "%Y-%m-%d"

Expand Down Expand Up @@ -118,10 +118,10 @@ def create_event(

# Place url and phone in desc if available:
if event.location.url:
description.append(f"Join online at: {event.location.url}")
description.append(l10n('join-online', {'url': event.location.url}))

if event.location.phone:
description.append(f"Join by phone: {event.location.phone}")
description.append(l10n('join-phone', {'phone': event.location.phone}))

body = {
"summary": event.title,
Expand Down
7 changes: 4 additions & 3 deletions backend/src/appointment/controller/data.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import csv
import datetime
from io import StringIO, BytesIO
from zipfile import ZipFile

from ..database import repo
from ..database.models import Subscriber
from ..download_readme import get_download_readme
from ..database.schemas import Subscriber
from ..exceptions.account_api import AccountDeletionPartialFail, AccountDeletionSubscriberFail
from ..l10n import l10n


def model_to_csv_buffer(models):
Expand Down Expand Up @@ -70,7 +71,7 @@ def download(db, subscriber: Subscriber):
data_zip.writestr("external_connection.csv", external_connections_buffer.getvalue())
data_zip.writestr("schedules.csv", schedules_buffer.getvalue())
data_zip.writestr("availability.csv", availability_buffer)
data_zip.writestr("readme.txt", get_download_readme())
data_zip.writestr("readme.txt", l10n('account-data-readme', {'download_time': datetime.datetime.now(datetime.UTC)}))

# Return our zip buffer
return zip_buffer
Expand Down
Loading

0 comments on commit e3abe71

Please sign in to comment.