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

Configure Feed

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

feat: now working join and leave

+151 -166
+2 -3
kefi/migrations/env.py
··· 16 16 from sqlmodel import SQLModel 17 17 18 18 from kefi.config import settings 19 - from kefi.models.core.models import Transaction, User # noqa 20 - from kefi.models.kudos.models import Action # noqa 21 - from kefi.models.plazas.models import Attendance, Plaza # noqa 19 + from kefi.models.database import Action # noqa 20 + from kefi.models.database import Attendance, Plaza, Transaction, User # noqa 22 21 23 22 config.set_main_option("sqlalchemy.url", settings.db_url()) 24 23 target_metadata = SQLModel.metadata
+1 -1
kefi/models/core/helpers.py
··· 3 3 4 4 from kefi.config import settings 5 5 from kefi.dependencies import SlashCommandParams 6 - from kefi.models.core.models import Transaction, User 6 + from kefi.models.database import Transaction, User 7 7 from kefi.routers.responses import ResetResponse 8 8 from kefi.slack import Slack 9 9
-73
kefi/models/core/models.py
··· 1 - from typing import TYPE_CHECKING, Optional 2 - 3 - from sqlalchemy import Column, String 4 - from sqlmodel import Field, Relationship, SQLModel 5 - 6 - if TYPE_CHECKING: 7 - from kefi.models.kudos.models import Action 8 - from kefi.models.plazas.models import Attendance 9 - 10 - 11 - class User(SQLModel, table=True): 12 - """A slack user.""" 13 - 14 - id: int | None = Field(default=None, primary_key=True) 15 - first_name: str | None = "" 16 - last_name: str | None = "" 17 - slack_user_id: str = Field(sa_column=Column(String(100), unique=True, index=True)) 18 - slack_username: str | None = "" 19 - transactions: list["Transaction"] = Relationship( 20 - back_populates="user", 21 - sa_relationship_kwargs=dict(foreign_keys="[Transaction.user_id]"), 22 - ) 23 - transactions_sent: list["Transaction"] = Relationship( 24 - back_populates="sender", 25 - sa_relationship_kwargs=dict(foreign_keys="[Transaction.sender_id]"), 26 - ) 27 - transactions_received: list["Transaction"] = Relationship( 28 - back_populates="receiver", 29 - sa_relationship_kwargs=dict(foreign_keys="[Transaction.receiver_id]"), 30 - ) 31 - is_admin: bool = False 32 - attendances: list["Attendance"] = Relationship(back_populates="user") 33 - 34 - def get_short_name(self): 35 - """Gets the short name of the user.""" 36 - return self.first_name or self.slack_username 37 - 38 - 39 - class Transaction(SQLModel, table=True): 40 - """A transaction is a kefi movement, form the system to a user or from a user to 41 - another. 42 - """ 43 - 44 - id: int | None = Field(default=None, primary_key=True) 45 - amount: int 46 - user_id: int = Field(foreign_key="user.id") 47 - user: User = Relationship( 48 - back_populates="transactions", 49 - sa_relationship_kwargs=dict(foreign_keys="[Transaction.user_id]"), 50 - ) 51 - message: str | None = Field(default=None) 52 - 53 - # Action, the reference of the action used to send the transaction 54 - action_id: int | None = Field(default=None, foreign_key="action.id") 55 - action: Optional["Action"] = Relationship(back_populates="transactions") 56 - 57 - # Attendance, the reference of the attendance used to send the transaction 58 - attendance_id: int | None = Field(default=None, foreign_key="attendance.id") 59 - attendance: Optional["Attendance"] = Relationship(back_populates="transactions") 60 - 61 - # Sender user, if not defined, is a system transaction 62 - sender_id: int | None = Field(default=None, foreign_key="user.id") 63 - sender: User | None = Relationship( 64 - back_populates="transactions_sent", 65 - sa_relationship_kwargs=dict(foreign_keys="[Transaction.sender_id]"), 66 - ) 67 - 68 - # Receiver user 69 - receiver_id: int | None = Field(default=None, foreign_key="user.id") 70 - receiver: User | None = Relationship( 71 - back_populates="transactions_received", 72 - sa_relationship_kwargs=dict(foreign_keys="[Transaction.receiver_id]"), 73 - )
+121 -1
kefi/models/database.py
··· 1 - from sqlmodel import create_engine 1 + import datetime 2 + from typing import Optional 3 + 4 + from sqlalchemy import Column, String 5 + from sqlmodel import Field, Relationship, SQLModel, UniqueConstraint, create_engine 2 6 3 7 from kefi.config import settings 4 8 5 9 # Creates the engine from the database url, to allow to create a session with 6 10 # the database 7 11 engine = create_engine(settings.db_url()) 12 + 13 + 14 + class User(SQLModel, table=True): 15 + """A slack user.""" 16 + 17 + id: int | None = Field(default=None, primary_key=True) 18 + first_name: str | None = "" 19 + last_name: str | None = "" 20 + slack_user_id: str = Field(sa_column=Column(String(100), unique=True, index=True)) 21 + slack_username: str | None = "" 22 + transactions: list["Transaction"] = Relationship( 23 + back_populates="user", 24 + sa_relationship_kwargs=dict(foreign_keys="[Transaction.user_id]"), 25 + ) 26 + transactions_sent: list["Transaction"] = Relationship( 27 + back_populates="sender", 28 + sa_relationship_kwargs=dict(foreign_keys="[Transaction.sender_id]"), 29 + ) 30 + transactions_received: list["Transaction"] = Relationship( 31 + back_populates="receiver", 32 + sa_relationship_kwargs=dict(foreign_keys="[Transaction.receiver_id]"), 33 + ) 34 + is_admin: bool = False 35 + attendances: list["Attendance"] = Relationship(back_populates="user") 36 + 37 + def get_short_name(self): 38 + """Gets the short name of the user.""" 39 + return self.first_name or self.slack_username 40 + 41 + 42 + class Transaction(SQLModel, table=True): 43 + """A transaction is a kefi movement, form the system to a user or from a user to 44 + another. 45 + """ 46 + 47 + id: int | None = Field(default=None, primary_key=True) 48 + amount: int 49 + user_id: int = Field(foreign_key="user.id") 50 + user: User = Relationship( 51 + back_populates="transactions", 52 + sa_relationship_kwargs=dict(foreign_keys="[Transaction.user_id]"), 53 + ) 54 + message: str | None = Field(default=None) 55 + 56 + # Action, the reference of the action used to send the transaction 57 + action_id: int | None = Field(default=None, foreign_key="action.id") 58 + action: Optional["Action"] = Relationship(back_populates="transactions") 59 + 60 + # Attendance, the reference of the attendance used to send the transaction 61 + attendance_id: int | None = Field(default=None, foreign_key="attendance.id") 62 + attendance: Optional["Attendance"] = Relationship(back_populates="transactions") 63 + 64 + # Sender user, if not defined, is a system transaction 65 + sender_id: int | None = Field(default=None, foreign_key="user.id") 66 + sender: User | None = Relationship( 67 + back_populates="transactions_sent", 68 + sa_relationship_kwargs=dict(foreign_keys="[Transaction.sender_id]"), 69 + ) 70 + 71 + # Receiver user 72 + receiver_id: int | None = Field(default=None, foreign_key="user.id") 73 + receiver: User | None = Relationship( 74 + back_populates="transactions_received", 75 + sa_relationship_kwargs=dict(foreign_keys="[Transaction.receiver_id]"), 76 + ) 77 + 78 + 79 + class Action(SQLModel, table=True): 80 + """Each action a user can perform to give kefis to another user.""" 81 + 82 + id: int | None = Field(default=None, primary_key=True) 83 + keyword: str = Field(sa_column=Column(String(100), unique=True, index=True)) 84 + amount: int 85 + transactions: list["Transaction"] = Relationship(back_populates="action") 86 + # Responses data 87 + header_template: str = "" 88 + message_template: str = "" 89 + context_template: str = "" 90 + image: str | None 91 + text: str | None 92 + 93 + 94 + class Plaza(SQLModel, table=True): 95 + """A plaza is a meeting session that represents a moment scheduled to create a 96 + group call with the different groups of attendees. 97 + """ 98 + 99 + id: int | None = Field(default=None, primary_key=True) 100 + date: datetime.datetime = Field( 101 + unique=True, index=True 102 + ) # When the meetings are going to be created 103 + attendances: list["Attendance"] = Relationship(back_populates="plaza") 104 + 105 + 106 + class Attendance(SQLModel, table=True): 107 + """An attendance is a user who has spent kefis to be part of a meetings session in 108 + the plaza. 109 + """ 110 + 111 + __table_args__ = ( 112 + UniqueConstraint("plaza_id", "user_id", name="plaza_id_user_id_unique"), 113 + ) 114 + id: int | None = Field(default=None, primary_key=True) 115 + # Meeting session that the attendance makes reference 116 + plaza_id: int = Field(foreign_key="plaza.id") 117 + plaza: Plaza = Relationship( 118 + back_populates="attendances", 119 + sa_relationship_kwargs=dict(foreign_keys="[Attendance.plaza_id]"), 120 + ) 121 + # User who is going to attend to the meeting session 122 + user_id: int = Field(foreign_key="user.id") 123 + user: User = Relationship( 124 + back_populates="attendances", 125 + sa_relationship_kwargs=dict(foreign_keys="[Attendance.user_id]"), 126 + ) 127 + transactions: list["Transaction"] = Relationship(back_populates="attendance")
+1 -2
kefi/models/kudos/helpers.py
··· 2 2 3 3 from kefi.constants import Command 4 4 from kefi.models.core.helpers import available_balance 5 - from kefi.models.core.models import Transaction, User 6 - from kefi.models.kudos.models import Action 5 + from kefi.models.database import Action, Transaction, User 7 6 from kefi.routers.responses import ActionResponse 8 7 from kefi.slack import Slack 9 8
-22
kefi/models/kudos/models.py
··· 1 - from typing import TYPE_CHECKING 2 - 3 - from sqlalchemy import Column, String 4 - from sqlmodel import Field, Relationship, SQLModel 5 - 6 - if TYPE_CHECKING: 7 - from kefi.models.core.models import Transaction 8 - 9 - 10 - class Action(SQLModel, table=True): 11 - """Each action a user can perform to give kefis to another user.""" 12 - 13 - id: int | None = Field(default=None, primary_key=True) 14 - keyword: str = Field(sa_column=Column(String(100), unique=True, index=True)) 15 - amount: int 16 - transactions: list["Transaction"] = Relationship(back_populates="action") 17 - # Responses data 18 - header_template: str = "" 19 - message_template: str = "" 20 - context_template: str = "" 21 - image: str | None 22 - text: str | None
+2 -3
kefi/models/plazas/helpers.py
··· 7 7 from kefi.config import settings 8 8 from kefi.models.core.exceptions import NotEnoughKefi 9 9 from kefi.models.core.helpers import available_balance 10 - from kefi.models.core.models import Transaction, User 10 + from kefi.models.database import Attendance, Plaza, Transaction, User 11 11 from kefi.models.plazas.exceptions import AlreadyAttending, NotAttending 12 - from kefi.models.plazas.models import Attendance, Plaza 13 12 14 13 15 14 def generate_random_meet() -> str: ··· 83 82 attendance = attendance_results.first() 84 83 if not attendance: 85 84 raise NotAttending("The user is not in the plaza") 86 - session.delete(attendance) 87 85 transaction_query = select(Transaction).filter( 88 86 Transaction.attendance == attendance, Transaction.user == user 89 87 ) 90 88 transaction_results = session.exec(transaction_query) 91 89 transaction = transaction_results.first() 92 90 session.delete(transaction) 91 + session.delete(attendance)
-46
kefi/models/plazas/models.py
··· 1 - import datetime 2 - from typing import TYPE_CHECKING 3 - 4 - from sqlmodel import Field, Relationship, SQLModel, UniqueConstraint 5 - 6 - from kefi.models.core.models import User 7 - 8 - if TYPE_CHECKING: 9 - 10 - from kefi.models.core.models import Transaction 11 - 12 - 13 - class Plaza(SQLModel, table=True): 14 - """A plaza is a meeting session that represents a moment scheduled to create a 15 - group call with the different groups of attendees. 16 - """ 17 - 18 - id: int | None = Field(default=None, primary_key=True) 19 - date: datetime.datetime = Field( 20 - unique=True, index=True 21 - ) # When the meetings are going to be created 22 - attendances: list["Attendance"] = Relationship(back_populates="plaza") 23 - 24 - 25 - class Attendance(SQLModel, table=True): 26 - """An attendance is a user who has spent kefis to be part of a meetings session in 27 - the plaza. 28 - """ 29 - 30 - __table_args__ = ( 31 - UniqueConstraint("plaza_id", "user_id", name="plaza_id_user_id_unique"), 32 - ) 33 - id: int | None = Field(default=None, primary_key=True) 34 - # Meeting session that the attendance makes reference 35 - plaza_id: int = Field(foreign_key="plaza.id") 36 - plaza: Plaza = Relationship( 37 - back_populates="attendances", 38 - sa_relationship_kwargs=dict(foreign_keys="[Attendance.plaza_id]"), 39 - ) 40 - # User who is going to attend to the meeting session 41 - user_id: int = Field(foreign_key="user.id") 42 - user: User = Relationship( 43 - back_populates="attendances", 44 - sa_relationship_kwargs=dict(foreign_keys="[Attendance.user_id]"), 45 - ) 46 - transactions: list["Transaction"] = Relationship(back_populates="attendance")
+10 -4
kefi/routers/helpers.py
··· 193 193 def interaction_block_actions(self) -> list: 194 194 return [] 195 195 196 - def _view_submission_meet_join(self, view_id: str): 196 + def _view_submission_meet_join(self, view_id: str) -> list | dict: 197 197 """Joins the plaza.""" 198 198 try: 199 199 create_attendance(user=self.user, session=self.session) 200 + return {"response_action": "clear"} 200 201 except NotEnoughKefi: 201 202 ... 202 203 except AlreadyAttending: 203 204 ... 205 + return [] 204 206 205 - def _view_submission_meet_leave(self, view_id: str): 207 + def _view_submission_meet_leave(self, view_id: str) -> list | dict: 206 208 """Leaves the plaza.""" 207 209 try: 208 210 delete_attendance(user=self.user, session=self.session) 211 + return {"response_action": "clear"} 209 212 except NotAttending: 210 213 ... 214 + return [] 211 215 212 - def interaction_view_submission(self) -> list: 216 + def interaction_view_submission(self) -> list | dict: 213 217 """Handles the submissions from a view.""" 214 218 view_id = self.payload["view"]["id"] 215 219 view_callback_id = self.payload["view"]["callback_id"] 216 220 try: 217 - getattr(self, f"_view_submission_{view_callback_id}")(view_id=view_id) 221 + return getattr(self, f"_view_submission_{view_callback_id}")( 222 + view_id=view_id 223 + ) 218 224 except AttributeError: 219 225 ... 220 226 return []
+1 -2
kefi/routers/responses.py
··· 1 - from kefi.models.core.models import User 2 - from kefi.models.kudos.models import Action 1 + from kefi.models.database import Action, User 3 2 from kefi.models.outputs import ( 4 3 Context, 5 4 Header,
+1 -1
kefi/tests/test_commands.py
··· 5 5 from sqlmodel.sql.expression import select 6 6 7 7 from kefi.models.core.helpers import available_balance, reset_wallets 8 - from kefi.models.core.models import Transaction, User 8 + from kefi.models.database import Transaction, User 9 9 from kefi.routers.helpers import Command 10 10 from kefi.tests.helpers import generate_command 11 11
+1 -3
kefi/tests/test_models.py
··· 10 10 recharge_wallets, 11 11 reset_wallets, 12 12 ) 13 - from kefi.models.core.models import Transaction, User 13 + from kefi.models.database import Action, Attendance, Plaza, Transaction, User 14 14 from kefi.models.kudos.helpers import send_action 15 - from kefi.models.kudos.models import Action 16 15 from kefi.models.plazas.exceptions import AlreadyAttending 17 16 from kefi.models.plazas.helpers import create_attendance, get_or_create_current_plaza 18 - from kefi.models.plazas.models import Attendance, Plaza 19 17 20 18 21 19 def test_available_balance(session: Session):
+1 -2
kefi/tests/test_responses.py
··· 3 3 from sqlmodel import Session, select 4 4 5 5 from kefi.constants import Command 6 - from kefi.models.core.models import User 6 + from kefi.models.database import Action, User 7 7 from kefi.models.kudos.helpers import create_default_actions 8 - from kefi.models.kudos.models import Action 9 8 from kefi.routers.responses import ActionResponse 10 9 11 10
+10 -3
manage.py
··· 11 11 from sqlmodel import Session, select 12 12 13 13 from kefi.config import settings 14 - from kefi.models.core.models import Transaction, User 15 - from kefi.models.database import engine 16 - from kefi.models.kudos.models import Action 14 + from kefi.models.database import ( 15 + Action, 16 + Attendance, 17 + Plaza, 18 + Transaction, 19 + User, 20 + engine, 21 + ) 17 22 18 23 session = Session(engine) 19 24 return { 20 25 "Action": Action, 21 26 "Transaction": Transaction, 27 + "Plaza": Plaza, 28 + "Attendance": Attendance, 22 29 "User": User, 23 30 "settings": settings, 24 31 "engine": engine,