Python backend for a Slack's kudos plugin.
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Merge commit '11c4a68d3b3014ede892118adeb863e70ccca380' into feature/feedback

+76 -8
+1 -1
README.md
··· 110 110 ``` 111 111 112 112 ```python 113 - from kefi.models.helpers import notify_reset_wallet, reset_wallets 113 + from kefi.models.helpers import reset_wallets 114 114 reset_wallets(session) 115 115 session.commit() 116 116 ```
+1
kefi/config/__init__.py
··· 16 16 PLAZA_PRICE: int = 10 17 17 PLAZA_DEFAULT_HOUR: int = 10 18 18 PLAZA_DEFAULT_MINUTE: int = 0 19 + PLAZA_SIZE: int = 3 19 20 20 21 class Config: 21 22 env_file = ".env"
+7
kefi/models/database.py
··· 1 1 import datetime 2 2 from typing import Optional 3 3 4 + import pytz 4 5 from sqlalchemy import Column, String 5 6 from sqlmodel import Field, Relationship, SQLModel, UniqueConstraint, create_engine 6 7 ··· 101 102 unique=True, index=True 102 103 ) # When the meetings are going to be created 103 104 attendances: list["Attendance"] = Relationship(back_populates="plaza") 105 + 106 + def local_date(self) -> datetime.datetime: 107 + """Gets the local date, using default Europe/Madrid timezone.""" 108 + return self.date.replace(tzinfo=pytz.utc).astimezone( 109 + pytz.timezone("Europe/Madrid") 110 + ) 104 111 105 112 106 113 class Attendance(SQLModel, table=True):
+43 -3
kefi/models/plazas/helpers.py
··· 1 1 import datetime 2 + import random 2 3 import uuid 3 4 4 5 import pytz ··· 9 10 from kefi.models.core.helpers import available_balance 10 11 from kefi.models.database import Attendance, Plaza, Transaction, User 11 12 from kefi.models.plazas.exceptions import AlreadyAttending, NotAttending 13 + from kefi.slack import Slack 12 14 13 15 14 - def generate_random_meet() -> str: 15 - return f"http://g.co/meet/kefi-plaza-{str(uuid.uuid4())}" 16 + def generate_random_meet() -> tuple[str, str]: 17 + """Generates a call id and a link to this meet id.""" 18 + meet_id = str(uuid.uuid4()) 19 + return meet_id, f"http://g.co/meet/kefi-plaza-{meet_id}" 16 20 17 21 18 22 def next_plaza_appointment() -> datetime.datetime: ··· 29 33 30 34 def get_or_create_current_plaza(session: Session) -> Plaza: 31 35 """Gets the current available plaza.""" 32 - now = datetime.datetime.now() 36 + now = datetime.datetime.now(tz=pytz.utc) 33 37 query = select(Plaza).filter(Plaza.date >= now).order_by("date") 34 38 results = session.exec(query) 35 39 first = results.first() ··· 89 93 transaction = transaction_results.first() 90 94 session.delete(transaction) 91 95 session.delete(attendance) 96 + 97 + 98 + def plaza_groups(session: Session, plaza: Plaza | None = None) -> list[list]: 99 + """Generates the groups for the current plaza or the given plaza.""" 100 + current_plaza: Plaza = plaza or get_or_create_current_plaza(session=session) 101 + query = select(Attendance).filter(Attendance.plaza == current_plaza) 102 + results = session.exec(query).all() 103 + users = [attendance.user for attendance in results] 104 + random.shuffle(users) 105 + return [ 106 + users[index : index + settings.PLAZA_SIZE] 107 + for index in range(0, len(users), settings.PLAZA_SIZE) 108 + ] 109 + 110 + 111 + def notify_plaza(session: Session, plaza: Plaza | None = None): 112 + slack = Slack() 113 + current_plaza: Plaza = plaza or get_or_create_current_plaza(session=session) 114 + groups = plaza_groups(session=session, plaza=current_plaza) 115 + for group in groups: 116 + slack_users = [user.slack_user_id for user in group] 117 + meet_id, meet_url = generate_random_meet() 118 + call = slack.create_group_call( 119 + url=meet_url, 120 + users=slack_users, 121 + external_unique_id=meet_id, 122 + ) 123 + slack.notify_call(users=slack_users, call_id=call["id"]) 124 + 125 + 126 + def select_current_plaza(session: Session) -> Plaza | None: 127 + """Gets the curren plaza that have to ve executed.""" 128 + now = datetime.datetime.now(tz=pytz.utc).replace(second=0, microsecond=0) 129 + query = select(Plaza).filter(Plaza.date == now).order_by("date") 130 + results = session.exec(query) 131 + return results.first()
+4 -2
kefi/routers/views.py
··· 18 18 def __init__(self, *args, **kwargs): 19 19 session = kwargs.get("session") 20 20 next_plaza = get_or_create_current_plaza(session=session) 21 + date = next_plaza.local_date() 21 22 super().__init__( 22 23 callback_id=ViewType.JOIN_MEET, 23 24 type="modal", ··· 33 34 DividerBlock(), 34 35 SectionBlock( 35 36 text=MarkdownTextObject( 36 - text=f"*Próximo encuentro*\n{next_plaza.date.strftime('%A, %-d %B')}\n{next_plaza.date.strftime('%H:%M')}\n" 37 + text=f"*Próximo encuentro*\n{date.strftime('%A, %-d %B')}\n{date.strftime('%H:%M')}\n" 37 38 ), 38 39 accessory=ImageElement( 39 40 image_url="https://api.slack.com/img/blocks/bkb_template_images/notifications.png", ··· 48 49 def __init__(self, *args, **kwargs): 49 50 session = kwargs.get("session") 50 51 next_plaza = get_or_create_current_plaza(session=session) 52 + date = next_plaza.local_date() 51 53 super().__init__( 52 54 callback_id=ViewType.LEAVE_MEET, 53 55 type="modal", ··· 63 65 DividerBlock(), 64 66 SectionBlock( 65 67 text=MarkdownTextObject( 66 - text=f"*Kefi Plaza*\n{next_plaza.date.strftime('%A, %-d %B')}\n{next_plaza.date.strftime('%H:%M')}\n" 68 + text=f"*Kefi Plaza*\n{date.strftime('%A, %-d %B')}\n{date.strftime('%H:%M')}\n" 67 69 ), 68 70 accessory=ImageElement( 69 71 image_url="https://api.slack.com/img/blocks/bkb_template_images/notifications.png",
+5 -1
kefi/tasks/main.py
··· 4 4 5 5 from kefi.config import settings 6 6 from kefi.models.database import engine 7 + from kefi.tasks.plazas import runs_plaza 7 8 from kefi.tasks.wallets import recharge_wallets_task 8 9 9 10 ··· 18 19 19 20 class WorkerSettings: 20 21 redis_settings = RedisSettings(host=settings.REDIS_HOST) 21 - cron_jobs = [cron(recharge_wallets_task, day=1, hour=0, minute=0)] # type: ignore 22 + cron_jobs = [ 23 + cron(recharge_wallets_task, day=1, hour=0, minute=0), # type: ignore 24 + cron(runs_plaza, minute=0), # type: ignore 25 + ] 22 26 on_startup = startup 23 27 on_shutdown = shutdown
+11
kefi/tasks/plazas.py
··· 1 + from kefi.models.plazas.helpers import notify_plaza, select_current_plaza 2 + 3 + 4 + async def runs_plaza(ctx): 5 + """Task to runs the plaza, that means, create the groups and send the call.""" 6 + session = ctx["session"] 7 + plaza = select_current_plaza(session=session) 8 + if plaza: 9 + notify_plaza(session=session, plaza=plaza) 10 + # In case the helper saves info 11 + ctx["session"].commit()
+4 -1
kefi/tests/test_models.py
··· 1 1 import datetime 2 2 3 + import pytz 3 4 from sqlmodel import Session 4 5 5 6 from kefi.config import settings ··· 104 105 def test_get_or_create_current_plaza_with_future_and_past(session: Session): 105 106 first_plaza = get_or_create_current_plaza(session=session) 106 107 session.add(Plaza(date=first_plaza.date + datetime.timedelta(days=3))) 107 - session.add(Plaza(date=datetime.datetime.now() - datetime.timedelta(days=3))) 108 + session.add( 109 + Plaza(date=datetime.datetime.now(tz=pytz.utc) - datetime.timedelta(days=3)) 110 + ) 108 111 assert isinstance(first_plaza, Plaza) 109 112 second_plaza = get_or_create_current_plaza(session=session) 110 113 assert isinstance(second_plaza, Plaza)