Skip to content

Commit 265a159

Browse files
authored
feat: MPV 2 Done, Getting ready for deploy
* feat: admin panel, meetings list * feat: meeting info, get title * feat: assign tutor & refactor * feat: dynamic meetings list * feat: meetinf info change, categories * fix(prepare.py): poetry > uv in ensure_pre_commit_hooks * chore(showcase): admin logic on /start * feat: authentication * feat: logging to AutoAuth and AuthGuard middlewares * feat: tutors repository * feat: use cases for tutors repository * feat: tutors list in dialog * feat: meetings_repo * feat: tutors router * feat: datepicker for admin * feat: RedisStorage, DTOs * refactor: meetings, meeting_change dialogs extracted * refactor: tutors dialog, funcs of admin is extracted * feat: meetings functionality for tutors * feat: announce meetings logic, notifications about tutors * feat: meeting deletion * feat: time (not only date) added to meeting * feat: meeting scheduling * feat: attendance system * feat: room data in meeting_change * refactor: handles in dialogs * refactor: dialog_extensions and handles * feat: student_meetings * feat: tutors_profile dialog * feat: support_daemon * refactor: use_cases * docs: database design (erd, tables) * feat: updated database lets not talk about the size of this * feat: mock auth middleware Added MockAutoAuthMiddleware that bypasses InNoHassle Accounts for easier testing * feat: student disciplines * feat: NotificationManager initialised - done 50% of messages * feat: notifications done 90% - on startup/shutdown notifications are sent - practically all texts are written * feat: notifications in scheduler * feat: refactor attendance and meeting handling logic; improve notification system * feat: enhance meeting notifications; add discipline info and update notification logic * fix: meeting retrieval join discipline table * feat: working links from notifications via start payload * feat: add tutor profile visibility and control logic in meetings and student meetings * fix: computed_field to full_name in models * feat: implement open_tutor_profile function and update profile button logic * fix: add room validation to meeting announcement logic * feat: tutor profile list for students * refactor: remove support daemon functionality and related configurations it become obvious, that mock auth middleware is way more effecient :) * feat: student meetings list filtered and sorted * fix: HTML escaping and data formatting * feat: notification bot polling and /start handling * fix: exclude unnecessary fields from meeting model dump in update method * fix: delete webhook in notification_manager start_polling * feat: tutor now can create meetings tutors are autoassigned to created meetings * fix: admin notified only after meeting approval (announce) * feat: add MeetingState APPROVING * feat: meeting approval process 50% * feat: implement meeting approval request and notification system * feat: meeting discard reason input and formatting * feat: list of Approving meetings * feat: sort by datetime for tutor&admin meetings list * feat: rename delete_meeting to cancel_meeting and update related notifications * chore(launch.json): debug in internalConsole instead of integratedTerminal should I even commit this? * feat: full house logging - logging for most of the bot functions - removed unused functions from utils.py * refactor: unite student, tutor, admin routers into root dialog, fixed auth issues * feat: integration of notifications with settings - link to bot in settings - query answers on notifications toggle - special answers on links from control bot - redirect from notifications bot directly to settings * docs: generate bot features and texts document for meeting soon * feat: add notification_bot_status to student model and database schema * feat: notification_manager can change notification_bot_status for students * feat: receive_notifications setting now does checks via notification_manager * feat: add saw_guide field to Student model and database schema * feat: implement guide dialog with states, getters, and handlers * docs: texts_translation.csv for the meeting * fix: check if meeting not APPROVING (issue #52) * feat: sync admins in db with config file * feat: enhance admin and student repositories with sync functionality * chore: add logging to file * fix: fixes after AT meeting - #55 - #56 - #59 * feat: i18n middlewares, other utils; cover root dialog * feat: i18n for root handles * fix: correct translation return value in ExtendedDialogManager * feat: i18n for discipline picker; UnpackedList widget * feat: i18n for authentication dialog * feat: implement i18n for meetings dialog, MeetingInfo widget * feat: i18n for change_meeting dialog * feat: i18n for guide dialog * feat: i18n for student_meetings dialog * feat: i18n for attendance dialog * feat: i18n for tutors and tutors_profile dialogs, bugfix in discipline_picker * fix: some fixes to current layout * feat: i18n for notifications * feat: changes in meeting_change, title & discipline, room & date TODO: - create new notification for room & date change - create the admin approval mechanism * feat: MeetingUpdate model * feat: approve updates to meetings - Implemented `clear_aiogram_redis.py` to remove specific keys from Redis related to aiogram and aiogram_dialog. - Added functionality for dry-run mode to preview keys without deletion. fix: Improve meeting info text rendering - Updated `MeetingInfoText` to return "Meeting" if rendered text is empty. fix: Prevent multiple pending updates for meetings - Added check in `on_date_room_btn` to ensure no pending updates exist before allowing changes. refactor: Consolidate meeting update logic - Removed redundant methods for updating meeting date and room. - Introduced `approve_meeting_update` to handle applying updates and notifying participants. feat: Enhance notification system for meeting updates - Added new notification texts and methods for sending update requests and approvals. - Updated notification manager to handle meeting update notifications. fix: Update i18n files to include new notification texts - Added new keys for meeting update notifications in `notifications.ftl`. refactor: Clean up meeting repository methods - Renamed `del_update` to `remove_update` for clarity. * feat: better l10n for notifications * feat: add language selection feature and update localization * feat: implement meeting reminders with 24h and 1h notifications * feat: deploy preparations - Changed project name in pyproject.toml from "aiogram-template" to "academic-tutorship-bot". - Added proxy_url field to settings.schema.yaml and updated config_schema.py. - Implemented proxy support in app.py for the bot session. - Updated i18n.py to use the new locales directory path. - Enhanced logging configuration to handle permission errors for file handlers. - Updated dependencies in uv.lock and pyproject.toml, including beanie and aiohttp-socks. * fix: before the deploy * fix: improve shutdown process with error handling for notifications and scheduler
1 parent dd311c5 commit 265a159

158 files changed

Lines changed: 13050 additions & 662 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.dockerignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
# Except code
55
!/src/
66
!/deploy/
7+
!/locales/
78
!/pyproject.toml
89
!/uv.lock
910
!logging.yaml

.gitignore

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,11 @@ __pycache__
99
.env
1010
settings.yaml
1111
.python-version
12+
*.sqlite
13+
14+
### Other ###
15+
.DS_Store
16+
try/
17+
*.drawio.bkp
18+
.logs/
19+
.VSCodeCounter

.pre-commit-config.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,6 @@ repos:
5252
language: system
5353
entry: uv run python ./scripts/alembic_auto_migrations.py
5454
pass_filenames: false
55-
files: storages/
55+
files: src/db/
5656
stages:
5757
- commit-msg

.vscode/launch.json

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{
2+
"version": "0.2.0",
3+
"configurations": [
4+
{
5+
"name": "Bot (debug)",
6+
"type": "debugpy",
7+
"request": "launch",
8+
"module": "src.bot",
9+
"cwd": "${workspaceFolder}",
10+
"justMyCode": true,
11+
"logToFile": true,
12+
"console": "internalConsole",
13+
"env": {
14+
"PYTHONUNBUFFERED": "1",
15+
"DEBUGPY_LOG_DIR": "${workspaceFolder}/.logs/debugpy"
16+
}
17+
}
18+
]
19+
}

alembic.ini

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
# A generic, single database configuration.
2+
3+
[alembic]
4+
# path to migration scripts
5+
script_location = alembic
6+
7+
# template used to generate migration file names; The default value is %%(rev)s_%%(slug)s
8+
# Uncomment the line below if you want the files to be prepended with date and time
9+
# see https://alembic.sqlalchemy.org/en/latest/tutorial.html#editing-the-ini-file
10+
# for all available tokens
11+
file_template = _%%(year)d_%%(month).2d_%%(day).2d_%%(hour).2d_%%(minute).2d_%%(slug)s
12+
13+
# sys.path path, will be prepended to sys.path if present.
14+
# defaults to the current working directory.
15+
prepend_sys_path = .
16+
17+
# timezone to use when rendering the date within the migration file
18+
# as well as the filename.
19+
# If specified, requires the python-dateutil library that can be
20+
# installed by adding `alembic[tz]` to the pip requirements
21+
# string value is passed to dateutil.tz.gettz()
22+
# leave blank for localtime
23+
# timezone =
24+
25+
# max length of characters to apply to the
26+
# "slug" field
27+
# truncate_slug_length = 40
28+
29+
# set to 'true' to run the environment during
30+
# the 'revision' command, regardless of autogenerate
31+
# revision_environment = false
32+
33+
# set to 'true' to allow .pyc and .pyo files without
34+
# a source .py file to be detected as revisions in the
35+
# versions/ directory
36+
# sourceless = false
37+
38+
# version location specification; This defaults
39+
# to alembic/versions. When using multiple version
40+
# directories, initial revisions must be specified with --version-path.
41+
# The path separator used here should be the separator specified by "version_path_separator" below.
42+
# version_locations = %(here)s/bar:%(here)s/bat:alembic/versions
43+
44+
# version path separator; As mentioned above, this is the character used to split
45+
# version_locations. The default within new alembic.ini files is "os", which uses os.pathsep.
46+
# If this key is omitted entirely, it falls back to the legacy behavior of splitting on spaces and/or commas.
47+
# Valid values for version_path_separator are:
48+
#
49+
# version_path_separator = :
50+
# version_path_separator = ;
51+
# version_path_separator = space
52+
version_path_separator = os # Use os.pathsep. Default configuration used for new projects.
53+
54+
# set to 'true' to search source files recursively
55+
# in each "version_locations" directory
56+
# new in Alembic version 1.10
57+
# recursive_version_locations = false
58+
59+
# the output encoding used when revision files
60+
# are written from script.py.mako
61+
# output_encoding = utf-8
62+
63+
[post_write_hooks]
64+
# post_write_hooks defines scripts or Python functions that are run
65+
# on newly generated revision scripts. See the documentation for further
66+
# detail and examples
67+
68+
# format using "black" - use the console_scripts runner, against the "black" entrypoint
69+
# hooks = black
70+
# black.type = console_scripts
71+
# black.entrypoint = black
72+
# black.options = -l 79 REVISION_SCRIPT_FILENAME
73+
74+
# lint with attempts to fix using "ruff" - use the exec runner, execute a binary
75+
hooks = ruff
76+
ruff.type = exec
77+
ruff.executable = poetry
78+
ruff.options = run ruff format REVISION_SCRIPT_FILENAME
79+
80+
# Logging configuration
81+
[loggers]
82+
keys = root,sqlalchemy,alembic
83+
84+
[handlers]
85+
keys = console
86+
87+
[formatters]
88+
keys = generic
89+
90+
[logger_root]
91+
level = WARN
92+
handlers = console
93+
qualname =
94+
95+
[logger_sqlalchemy]
96+
level = WARN
97+
handlers =
98+
qualname = sqlalchemy.engine
99+
100+
[logger_alembic]
101+
level = INFO
102+
handlers =
103+
qualname = alembic
104+
105+
[handler_console]
106+
class = StreamHandler
107+
args = (sys.stderr,)
108+
level = NOTSET
109+
formatter = generic
110+
111+
[formatter_generic]
112+
format = %(levelname)-5.5s [%(name)s] %(message)s
113+
datefmt = %H:%M:%S

alembic/README.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# Alembic
2+
3+
Based on [Async alembic configuration with ruff post-hook](https://gist.github.com/dantetemplar/cbef8b9f1d9d6cde7547629d6d85fcd1) gist.
4+
5+
### Autogenerate migration
6+
```bash
7+
alembic revision --autogenerate -m "<some message>"
8+
```
9+
### Upgrade database
10+
Upgrade to the latest revision
11+
```bash
12+
alembic upgrade head
13+
```
14+
Upgrade to the specific revision
15+
```bash
16+
alembic upgrade <revision>
17+
```
18+
19+
### Skip migration but apply it in database
20+
21+
Stamp will set the database alembic version to the specified revision but will not change schema.
22+
```bash
23+
alembic stamp <revision>
24+
```

alembic/env.py

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
import asyncio
2+
from logging.config import fileConfig
3+
from pathlib import Path
4+
from urllib.parse import urlsplit, urlunsplit
5+
6+
from sqlalchemy import pool
7+
from sqlalchemy.engine import Connection
8+
from sqlalchemy.ext.asyncio import async_engine_from_config
9+
10+
from alembic import context
11+
12+
# this is the Alembic Config object, which provides
13+
# access to the values within the .ini file in use.
14+
config = context.config
15+
16+
# Interpret the config file for Python logging.
17+
# This line sets up loggers basically.
18+
if config.config_file_name is not None:
19+
fileConfig(config.config_file_name)
20+
21+
# Read settings YAML file
22+
import os # noqa: E402
23+
24+
from yaml import safe_load # noqa: E402
25+
26+
27+
def _is_inside_docker() -> bool:
28+
return Path("/.dockerenv").exists()
29+
30+
31+
def _replace_hostname_with_localhost(url: str) -> str:
32+
parts = urlsplit(url)
33+
if not parts.netloc:
34+
return url
35+
userinfo = ""
36+
host_port = parts.netloc
37+
if "@" in parts.netloc:
38+
userinfo, host_port = parts.netloc.rsplit("@", 1)
39+
if host_port == "db" or host_port.startswith("db:"):
40+
host_port = host_port.replace("db", "localhost", 1)
41+
netloc = f"{userinfo}@{host_port}" if userinfo else host_port
42+
return urlunsplit((parts.scheme, netloc, parts.path, parts.query, parts.fragment))
43+
return url
44+
45+
46+
app_settings_path = Path(os.getenv("SETTINGS_PATH", "settings.yaml"))
47+
app_settings = safe_load(app_settings_path.read_text())
48+
49+
# Prefer explicit env override for migrations, then fallback to app settings.
50+
db_url = os.getenv("ALEMBIC_DB_URL") or os.getenv("DATABASE_URL") or app_settings["db_url"]
51+
52+
# Allow local migrations when settings use docker-compose hostname "db".
53+
if not _is_inside_docker():
54+
db_url = _replace_hostname_with_localhost(db_url)
55+
56+
config.set_main_option("sqlalchemy.url", db_url)
57+
58+
# add your model's MetaData object here
59+
# for 'autogenerate' support
60+
# from myapp import mymodel
61+
# target_metadata = mymodel.Base.metadata
62+
from src.db.schema import metadata # noqa: E402
63+
64+
target_metadata = metadata
65+
66+
67+
# other values from the config, defined by the needs of env.py,
68+
# can be acquired:
69+
# my_important_option = config.get_main_option("my_important_option")
70+
# ... etc.
71+
72+
73+
def run_migrations_offline() -> None:
74+
"""Run migrations in 'offline' mode.
75+
76+
This configures the context with just a URL
77+
and not an Engine, though an Engine is acceptable
78+
here as well. By skipping the Engine creation
79+
we don't even need a DBAPI to be available.
80+
81+
Calls to context.execute() here emit the given string to the
82+
script output.
83+
84+
"""
85+
url = config.get_main_option("sqlalchemy.url")
86+
context.configure(
87+
url=url,
88+
target_metadata=target_metadata,
89+
literal_binds=True,
90+
dialect_opts={"paramstyle": "named"},
91+
)
92+
93+
with context.begin_transaction():
94+
context.run_migrations()
95+
96+
97+
def do_run_migrations(connection: Connection) -> None:
98+
context.configure(
99+
connection=connection,
100+
target_metadata=target_metadata,
101+
process_revision_directives=process_revision_directives,
102+
)
103+
with context.begin_transaction():
104+
context.run_migrations()
105+
106+
107+
async def run_async_migrations() -> None:
108+
"""In this scenario we need to create an Engine
109+
and associate a connection with the context.
110+
"""
111+
112+
connectable = async_engine_from_config(
113+
config.get_section(config.config_ini_section, {}),
114+
prefix="sqlalchemy.",
115+
poolclass=pool.NullPool,
116+
)
117+
118+
async with connectable.connect() as connection:
119+
await connection.run_sync(do_run_migrations)
120+
121+
await connectable.dispose()
122+
123+
124+
def run_migrations_online() -> None:
125+
"""Run migrations in 'online' mode."""
126+
connectable = config.attributes.get("connection", None)
127+
128+
if connectable is None:
129+
asyncio.run(run_async_migrations())
130+
else:
131+
do_run_migrations(connectable)
132+
133+
134+
# https://stackoverflow.com/a/71212675/19566814
135+
# almost identical to Flask-Migrate (Thanks miguel!)
136+
# this callback is used to prevent an auto-migration from being generated
137+
# when there are no changes to the schema
138+
139+
140+
def process_revision_directives(context, revision, directives):
141+
if config.cmd_opts.autogenerate:
142+
script = directives[0]
143+
if script.upgrade_ops.is_empty():
144+
directives[:] = []
145+
print("No changes in schema detected.")
146+
147+
148+
if context.is_offline_mode():
149+
run_migrations_offline()
150+
else:
151+
run_migrations_online()

alembic/script.py.mako

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
"""${message}
2+
3+
Revision ID: ${up_revision}
4+
Revises: ${down_revision | comma,n}
5+
Create Date: ${create_date}
6+
7+
"""
8+
# ruff: noqa: E501
9+
from typing import Sequence, Union
10+
11+
from alembic import op
12+
import sqlalchemy as sa
13+
${imports if imports else ""}
14+
15+
# revision identifiers, used by Alembic.
16+
revision: str = ${repr(up_revision)}
17+
down_revision: Union[str, None] = ${repr(down_revision)}
18+
branch_labels: Union[str, Sequence[str], None] = ${repr(branch_labels)}
19+
depends_on: Union[str, Sequence[str], None] = ${repr(depends_on)}
20+
21+
22+
def upgrade() -> None:
23+
${upgrades if upgrades else "pass"}
24+
25+
26+
def downgrade() -> None:
27+
${downgrades if downgrades else "pass"}

alembic/versions/.gitkeep

Whitespace-only changes.

0 commit comments

Comments
 (0)