Skip to content

Commit

Permalink
Merge pull request #82 from caarmen/structured-app-conf
Browse files Browse the repository at this point in the history
Move some configuration out of the .env file and into a `config/app.yaml` file.
  • Loading branch information
caarmen authored Dec 14, 2024
2 parents ceed624 + e488760 commit bf986e7
Show file tree
Hide file tree
Showing 26 changed files with 328 additions and 179 deletions.
24 changes: 1 addition & 23 deletions .env.template
Original file line number Diff line number Diff line change
@@ -1,30 +1,8 @@
SERVER_URL=http://localhost:8000/
DATABASE_PATH=/tmp/data/slackhealthbot.db

WITHINGS_CLIENT_ID=0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef
WITHINGS_CLIENT_SECRET=0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef
WITHINGS_CALLBACK_URL=http://localhost:8000/

FITBIT_CLIENT_ID=01A2B3
FITBIT_CLIENT_SECRET=0123456789abcdef0123456789abcdef
FITBIT_CLIENT_SUBSCRIBER_VERIFICATION_CODE=0123456789abcdef0123456789abcdef0123456789abcdef1023456789abcdef
FITBIT_POLL_ENABLED=true
FITBIT_POLL_INTERVAL_S=3600

# For FITBIT_ACTIVITY_TYPE_IDS:
# See https://dev.fitbit.com/build/reference/web-api/activity/get-all-activity-types/
# for the list of all supported activity types and their ids.
# Some examples:
# 55001: Spinning
# 90013: Walk
# 90001: Bike
# 90019: Treadmill
# 1071: Outdoor Bike
FITBIT_REALTIME_ACTIVITY_TYPE_IDS=[55001, 90013]
FITBIT_DAILY_ACTIVITY_TYPE_IDS=[90019]
FITBIT_DAILY_ACTIVITY_POST_TIME=23:50
FITBIT_ACTIVITY_RECORD_HISTORY_DAYS=180

SLACK_WEBHOOK_URL=https://hooks.slack.com/services/XXXXXXXXX/XXXXXXXXXXX/abcdefghijklmnopqrstuvwx

SQL_LOG_LEVEL=WARNING
SLACK_WEBHOOK_URL=https://hooks.slack.com/services/XXXXXXXXX/XXXXXXXXXXX/abcdefghijklmnopqrstuvwx
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,5 @@
.coverage
reports/
__pycache__/
config/app-merged.yaml
config/app-custom.yaml
1 change: 1 addition & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ COPY requirements/prod.txt requirements.txt
RUN pip install -r requirements.txt

COPY slackhealthbot slackhealthbot
COPY config/app-default.yaml config/app-default.yaml
COPY templates templates
COPY alembic.ini alembic.ini
COPY alembic alembic
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ Pushes messages to a pre-selected Slack channel, when users log new weight data
* Create an application in the [Fitbit developer dashboard](https://dev.fitbit.com/apps/).
* Create an application [on Slack](https://api.slack.com/apps), with a webhook to post messages to a specific channel.
* Copy the `.env.template` file to `.env`, and modify the values.
* [Optional]: Copy the `config/app-custom.yaml.template` file to `app-custom.yaml`, and override any values which are defined in `config/app-default.yaml`.

## Retrieve the docker image

Expand All @@ -36,7 +37,7 @@ Create a folder on the host where the database will be saved: `/path/to/data/`.
Run the docker image.

```
docker run --detach --publish 8000:8000 -v `pwd`/.env:/app/.env -v /path/to/data/:/tmp/data ghcr.io/caarmen/slack-health-bot
docker run --detach --publish 8000:8000 -v `pwd`/.env:/app/.env -v `pwd`/app-custom.yaml:/app/config/app-custom.yaml -v /path/to/data/:/tmp/data ghcr.io/caarmen/slack-health-bot
```

## Using the application
Expand Down
3 changes: 3 additions & 0 deletions config/app-custom.yaml.template
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Place any overrides of the default values, from app-default.yaml, into this file.
logging:
sql_log_level: "DEBUG" # example override: set sql logging to DEBUG
52 changes: 52 additions & 0 deletions config/app-default.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# Configuration of the slack-health-bot application
server_url: "http://localhost:8000/" # The url to access the slack-health-bot server for login.
database_path: "/tmp/data/slackhealthbot.db" # The location to the database file.
logging:
sql_log_level: "WARNING"

# Withings-specific configuration:
# Note that secrets like the client id and client secret are configured in the .env file.
withings:
callback_url: "http://localhost:8000/" # The url that withings will call at the end of SSO.

# Fitbit-specific configuration:
# Note that secrets like the client id and client secret are configured in the .env file.
fitbit:
poll:
enabled: true # If your server can't receive webhook calls from fitbit, activate polling instead, to fetch data from fitbit.
interval_seconds: 3600 # How often to poll fitbit for data.

activities:
history_days: 180 # how far to look back to report new records of best times/durations/calories/etc.
daily_report_time: "23:50" # Time of day (HH:mm)to post daily reports to slack.

activity_types:
# Configuration specific to activity types.
#
# For fitbit activity type ids:
# See https://dev.fitbit.com/build/reference/web-api/activity/get-all-activity-types/
# for the list of all supported activity types and their ids.
# Some examples:
# 55001: Spinning
# 90013: Walk
# 90001: Bike
# 90019: Treadmill
# 1071: Outdoor Bike
#
# supported attributes:
# report_daily: whether a daily summary report should be posted to slack for this activity type
# report_realtime: whether a report should be posted to slack for this activity type as soon as we receive it from fitbit
- name: Treadmill
id: 90019
report_daily: true
report_realtime: false

- name: Spinning
id: 55001
report_daily: false
report_realtime: true

- name: Walk
id: 90013
report_daily: false
report_realtime: true
2 changes: 1 addition & 1 deletion requirements/prod.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ fastapi==0.115.6
httpx==0.27.2
itsdangerous==2.2.0
Jinja2==3.1.4
pydantic-settings==2.6.1
pydantic-settings[yaml]==2.6.1
pydantic==2.10.3
python-dotenv==1.0.1
python-multipart==0.0.19
Expand Down
2 changes: 1 addition & 1 deletion scripts/run_tests.sh
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
rm -rf reports
SQL_LOG_LEVEL=DEBUG python -m pytest \
LOGGING__SQL_LOG_LEVEL=DEBUG python -m pytest \
--numprocesses=auto \
--cov=slackhealthbot \
--cov-report=xml \
Expand Down
6 changes: 3 additions & 3 deletions slackhealthbot/data/database/connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@

from slackhealthbot.settings import settings

connection_url = f"sqlite+aiosqlite:///{settings.database_path}"
Path(settings.database_path).parent.mkdir(parents=True, exist_ok=True)
connection_url = f"sqlite+aiosqlite:///{settings.app_settings.database_path}"
Path(settings.app_settings.database_path).parent.mkdir(parents=True, exist_ok=True)
engine = create_async_engine(
connection_url,
connect_args={"check_same_thread": False},
Expand All @@ -16,7 +16,7 @@
autocommit=False, autoflush=False, bind=engine, future=True
)

if settings.sql_log_level.upper() == "DEBUG":
if settings.app_settings.logging.sql_log_level.upper() == "DEBUG":

def before_cursor_execute(_conn, _cursor, statement, parameters, *args):
logging.debug(f"{statement}; args={parameters}")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,8 @@ async def do(
await local_fitbit_repo.get_top_daily_activity_stats_by_user_and_activity_type(
fitbit_userid=fitbit_userid,
type_id=daily_activity.type_id,
since=now - dt.timedelta(days=settings.fitbit_activity_record_history_days),
since=now
- dt.timedelta(days=settings.app_settings.fitbit.activities.history_days),
)
)

Expand All @@ -68,5 +69,5 @@ async def do(
slack_alias=user_identity.slack_alias,
activity_name=activity_names.get(daily_activity.type_id, "Unknown"),
history=history,
record_history_days=settings.fitbit_activity_record_history_days,
record_history_days=settings.app_settings.fitbit.activities.history_days,
)
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,10 @@ async def do(
activity=new_activity_data,
)

if new_activity_data.type_id not in settings.fitbit_realtime_activity_type_ids:
if (
new_activity_data.type_id
not in settings.app_settings.fitbit_realtime_activity_type_ids
):
# This activity isn't to be posted in realtime to slack.
# We're done for now.
return
Expand All @@ -83,7 +86,9 @@ async def do(
fitbit_userid=fitbit_userid,
type_id=new_activity_data.type_id,
since=datetime.datetime.now(datetime.timezone.utc)
- datetime.timedelta(days=settings.fitbit_activity_record_history_days),
- datetime.timedelta(
days=settings.app_settings.fitbit.activities.history_days
),
)
)
await usecase_post_activity.do(
Expand All @@ -96,7 +101,7 @@ async def do(
all_time_top_activity_data=all_time_top_activity_stats,
recent_top_activity_data=recent_top_activity_stats,
),
record_history_days=settings.fitbit_activity_record_history_days,
record_history_days=settings.app_settings.fitbit.activities.history_days,
)

return new_activity_data
Expand All @@ -109,7 +114,7 @@ async def _is_new_valid_activity(
log_id: int,
) -> bool:
return (
type_id in settings.fitbit_activity_type_ids
type_id in settings.app_settings.fitbit_activity_type_ids
and not await repo.get_activity_by_user_and_log_id(
fitbit_userid=fitbit_userid,
log_id=log_id,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,6 @@ async def do(
message = f"""
Oh no <@{slack_alias}>, looks like you were logged out of {service}! 😳.
You'll need to log in again to get your reports:
{settings.server_url}v1/{service}-authorization/{slack_alias}
{settings.app_settings.server_url}v1/{service}-authorization/{slack_alias}
"""
await repo.post_message(message)
10 changes: 5 additions & 5 deletions slackhealthbot/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@

@asynccontextmanager
async def lifespan(_app: FastAPI):
logger.configure_logging(settings.sql_log_level)
logger.configure_logging(settings.app_settings.logging.sql_log_level)
oauth_withings.configure(
WithingsUpdateTokenUseCase(
request_context_withings_repository,
Expand All @@ -49,20 +49,20 @@ async def lifespan(_app: FastAPI):
)
)
schedule_task = None
if settings.fitbit_poll_enabled:
if settings.app_settings.fitbit.poll.enabled:
schedule_task = await fitbitpoll.schedule_fitbit_poll(
local_fitbit_repo_factory=fitbit_repository_factory(),
remote_fitbit_repo=get_remote_fitbit_repository(),
slack_repo=get_slack_repository(),
initial_delay_s=10,
)
daily_activity_task: Task | None = None
if settings.fitbit_daily_activity_type_ids:
if settings.app_settings.fitbit_daily_activity_type_ids:
daily_activity_task = await post_daily_activities(
local_fitbit_repo_factory=fitbit_repository_factory(),
activity_type_ids=set(settings.fitbit_daily_activity_type_ids),
activity_type_ids=set(settings.app_settings.fitbit_daily_activity_type_ids),
slack_repo=get_slack_repository(),
post_time=settings.fitbit_daily_activity_post_time,
post_time=settings.app_settings.fitbit.activities.daily_report_time,
)
yield
if schedule_task:
Expand Down
2 changes: 1 addition & 1 deletion slackhealthbot/remoteservices/api/slack/messageapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
async def post_message(message: str):
async with httpx.AsyncClient() as client:
await client.post(
url=str(settings.slack_webhook_url),
url=str(settings.secret_settings.slack_webhook_url),
json={
"text": message,
},
Expand Down
Loading

0 comments on commit bf986e7

Please sign in to comment.