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: refactor of models

+404 -275
+1 -1
kefi/dependencies.py
··· 1 1 from fastapi import Form 2 2 from sqlmodel import Session 3 3 4 - from kefi.models.database import engine 4 + from kefi.models import engine 5 5 6 6 7 7 class SlashCommandParams:
+3 -2
kefi/main.py
··· 1 1 from fastapi import FastAPI 2 2 from sqlmodel import Session 3 3 4 - from kefi.models.database import engine 5 - from kefi.models.helpers import create_default_actions, create_users 4 + from kefi.models import engine 5 + from kefi.models.core.helpers import create_users 6 + from kefi.models.kudos.helpers import create_default_actions 6 7 from kefi.routers import commands 7 8 8 9 app = FastAPI()
+2 -1
kefi/migrations/env.py
··· 16 16 from sqlmodel import SQLModel 17 17 18 18 from kefi.config import settings 19 - from kefi.models.database import Action, Transaction, User 19 + from kefi.models.core.database import Transaction, User 20 + from kefi.models.kudos.database import Action 20 21 21 22 config.set_main_option("sqlalchemy.url", settings.db_url()) 22 23 target_metadata = SQLModel.metadata
+96
kefi/migrations/versions/d3a04e3f4a1f_update_migrations.py
··· 1 + """update migrations 2 + 3 + Revision ID: d3a04e3f4a1f 4 + Revises: dc867102b767 5 + Create Date: 2022-11-04 12:55:36.744428 6 + 7 + """ 8 + import sqlalchemy as sa 9 + import sqlmodel 10 + from alembic import op 11 + 12 + # revision identifiers, used by Alembic. 13 + revision = "d3a04e3f4a1f" 14 + down_revision = "dc867102b767" 15 + branch_labels = None 16 + depends_on = None 17 + 18 + 19 + def upgrade(): 20 + # ### commands auto generated by Alembic - please adjust! ### 21 + op.alter_column( 22 + "action", "header_template", existing_type=sa.VARCHAR(), nullable=False 23 + ) 24 + op.alter_column( 25 + "action", "message_template", existing_type=sa.VARCHAR(), nullable=False 26 + ) 27 + op.alter_column( 28 + "action", "context_template", existing_type=sa.VARCHAR(), nullable=False 29 + ) 30 + op.drop_index("ix_action_amount", table_name="action") 31 + op.drop_index("ix_action_context_template", table_name="action") 32 + op.drop_index("ix_action_header_template", table_name="action") 33 + op.drop_index("ix_action_id", table_name="action") 34 + op.drop_index("ix_action_image", table_name="action") 35 + op.drop_index("ix_action_message_template", table_name="action") 36 + op.drop_index("ix_action_text", table_name="action") 37 + op.drop_index("ix_transaction_action_id", table_name="transaction") 38 + op.drop_index("ix_transaction_amount", table_name="transaction") 39 + op.drop_index("ix_transaction_id", table_name="transaction") 40 + op.drop_index("ix_transaction_message", table_name="transaction") 41 + op.drop_index("ix_transaction_receiver_id", table_name="transaction") 42 + op.drop_index("ix_transaction_sender_id", table_name="transaction") 43 + op.drop_index("ix_transaction_user_id", table_name="transaction") 44 + op.alter_column("user", "is_admin", existing_type=sa.BOOLEAN(), nullable=False) 45 + op.drop_index("ix_user_first_name", table_name="user") 46 + op.drop_index("ix_user_id", table_name="user") 47 + op.drop_index("ix_user_is_admin", table_name="user") 48 + op.drop_index("ix_user_last_name", table_name="user") 49 + op.drop_index("ix_user_slack_username", table_name="user") 50 + # ### end Alembic commands ### 51 + 52 + 53 + def downgrade(): 54 + # ### commands auto generated by Alembic - please adjust! ### 55 + op.create_index("ix_user_slack_username", "user", ["slack_username"], unique=False) 56 + op.create_index("ix_user_last_name", "user", ["last_name"], unique=False) 57 + op.create_index("ix_user_is_admin", "user", ["is_admin"], unique=False) 58 + op.create_index("ix_user_id", "user", ["id"], unique=False) 59 + op.create_index("ix_user_first_name", "user", ["first_name"], unique=False) 60 + op.alter_column("user", "is_admin", existing_type=sa.BOOLEAN(), nullable=True) 61 + op.create_index("ix_transaction_user_id", "transaction", ["user_id"], unique=False) 62 + op.create_index( 63 + "ix_transaction_sender_id", "transaction", ["sender_id"], unique=False 64 + ) 65 + op.create_index( 66 + "ix_transaction_receiver_id", "transaction", ["receiver_id"], unique=False 67 + ) 68 + op.create_index("ix_transaction_message", "transaction", ["message"], unique=False) 69 + op.create_index("ix_transaction_id", "transaction", ["id"], unique=False) 70 + op.create_index("ix_transaction_amount", "transaction", ["amount"], unique=False) 71 + op.create_index( 72 + "ix_transaction_action_id", "transaction", ["action_id"], unique=False 73 + ) 74 + op.create_index("ix_action_text", "action", ["text"], unique=False) 75 + op.create_index( 76 + "ix_action_message_template", "action", ["message_template"], unique=False 77 + ) 78 + op.create_index("ix_action_image", "action", ["image"], unique=False) 79 + op.create_index("ix_action_id", "action", ["id"], unique=False) 80 + op.create_index( 81 + "ix_action_header_template", "action", ["header_template"], unique=False 82 + ) 83 + op.create_index( 84 + "ix_action_context_template", "action", ["context_template"], unique=False 85 + ) 86 + op.create_index("ix_action_amount", "action", ["amount"], unique=False) 87 + op.alter_column( 88 + "action", "context_template", existing_type=sa.VARCHAR(), nullable=True 89 + ) 90 + op.alter_column( 91 + "action", "message_template", existing_type=sa.VARCHAR(), nullable=True 92 + ) 93 + op.alter_column( 94 + "action", "header_template", existing_type=sa.VARCHAR(), nullable=True 95 + ) 96 + # ### end Alembic commands ###
+7
kefi/models/__init__.py
··· 1 + from sqlmodel import create_engine 2 + 3 + from kefi.config import settings 4 + 5 + # Creates the engine from the database url, to allow to create a session with 6 + # the database 7 + engine = create_engine(settings.db_url())
kefi/models/core/__init__.py

This is a binary file and will not be displayed.

+130
kefi/models/core/helpers.py
··· 1 + from slack_sdk import WebClient 2 + from sqlmodel import Session, func, or_, select 3 + 4 + from kefi.config import settings 5 + from kefi.dependencies import SlashCommandParams 6 + from kefi.models.core.database import Transaction, User 7 + from kefi.routers.responses import ResetResponse 8 + from kefi.slack import Slack 9 + 10 + 11 + def get_or_create_from_command( 12 + command: SlashCommandParams, session: Session 13 + ) -> tuple["User", bool]: 14 + """Gets or create the user from the given command.""" 15 + query = select(User).filter(User.slack_user_id == command.user_id) 16 + user = session.exec(query).one_or_none() 17 + created = not user 18 + if not user: 19 + user = User(slack_user_id=command.user_id, slack_username=command.user_name) 20 + else: 21 + # Updates the slack username 22 + user.slack_username = command.user_name 23 + session.add(user) 24 + return (user, created) 25 + 26 + 27 + def available_balance(user: User, session: Session) -> int: 28 + """Obtains the available balance of the user, that means the balance not spend from 29 + the monthly received. 30 + """ 31 + query = select(func.sum(Transaction.amount)).filter( # type: ignore 32 + Transaction.user == user, 33 + or_(Transaction.sender == user, Transaction.sender == None), # type: ignore 34 + ) 35 + return session.exec(query).one() or 0 36 + 37 + 38 + def received_balance(user: User, session: Session) -> int: 39 + """Obtains the received balance of the user, that means the balance sent to the 40 + user. 41 + """ 42 + query = select(func.sum(Transaction.amount)).filter( # type: ignore 43 + Transaction.user == user, Transaction.receiver == user 44 + ) 45 + return session.exec(query).one() or 0 46 + 47 + 48 + def find_user_by_slack_user_id(slack_user_id: str, session: Session) -> User | None: 49 + """Gets the user using the Slack user id.""" 50 + query = select(User).filter(User.slack_user_id == slack_user_id) 51 + return session.exec(query).one_or_none() 52 + 53 + 54 + def find_user_by_slack_username(slack_username: str, session: Session) -> User | None: 55 + """Gets the user using the Slack username.""" 56 + query = select(User).filter(User.slack_username == slack_username) 57 + return session.exec(query).one_or_none() 58 + 59 + 60 + def create_users(session: Session) -> None: 61 + """Creates all the users in the team.""" 62 + # Gets the users 63 + client = WebClient(token=settings.SLACK_BOT_TOKEN) 64 + response = client.users_list(team_id=settings.SLACK_TEAM_ID) 65 + members = response["members"] 66 + while response["response_metadata"]["next_cursor"]: 67 + response = client.users_list(team_id=settings.SLACK_TEAM_ID) 68 + members += response["members"] 69 + members = list( 70 + filter(lambda user: not user["is_bot"] and not user["deleted"], members) 71 + ) 72 + # Create or updates the users 73 + for member in members: 74 + query = select(User).filter(User.slack_user_id == member["id"]) 75 + user = session.exec(query).one_or_none() 76 + if not user: 77 + user = User( 78 + slack_user_id=member["id"], 79 + slack_username=member["name"], 80 + first_name=member["profile"].get("first_name", ""), 81 + last_name=member["profile"].get("last_name", ""), 82 + is_admin=member["is_admin"], 83 + ) 84 + else: 85 + user.slack_username = member["name"] 86 + user.is_admin = member["is_admin"] 87 + user.first_name = member["profile"].get("first_name", "") 88 + user.last_name = member["profile"].get("last_name", "") 89 + session.add(user) 90 + 91 + 92 + def get_all_users(session: Session) -> list["User"]: 93 + """Gets the complete list of users.""" 94 + return session.exec(select(User)).all() 95 + 96 + 97 + def send_admin_amount(receiver: User, amount: int, session: Session) -> "Transaction": 98 + """Creates the transaction needed to send an amount to the receiver.""" 99 + receiver_transaction = Transaction(amount=amount, user=receiver) 100 + session.add(receiver_transaction) 101 + return receiver_transaction 102 + 103 + 104 + def recharge_wallets(amount: int, session: Session) -> None: 105 + """Recharge all the wallet with the given amount.""" 106 + query = select(User) 107 + users = session.exec(query).all() 108 + for user in users: 109 + transaction = Transaction(amount=amount, user=user) 110 + session.add(transaction) 111 + 112 + 113 + def reset_wallets(session: "Session") -> None: 114 + """Reset the wallets of all the users.""" 115 + users = session.exec(select(User)).all() 116 + for user in users: 117 + balance = available_balance(user=user, session=session) 118 + amount = settings.RECHARGE_KEFIS_AMOUNT - balance 119 + transaction = Transaction(amount=amount, user=user) 120 + session.add(transaction) 121 + 122 + 123 + def notify_reset_wallet(session: Session) -> None: 124 + """Sends notifications after the wallet was reset.""" 125 + amount = settings.RECHARGE_KEFIS_AMOUNT 126 + users = session.exec(select(User)).all() 127 + for user in users: 128 + slack = Slack() 129 + response = ResetResponse(amount=amount) 130 + slack.post_message_user(user.slack_user_id, blocks=response.render()["blocks"])
+6 -20
kefi/models/database.py kefi/models/core/database.py
··· 1 + from typing import TYPE_CHECKING, Optional 2 + 1 3 from sqlalchemy import Column, String 2 - from sqlmodel import Field, Relationship, SQLModel, create_engine 4 + from sqlmodel import Field, Relationship, SQLModel 3 5 4 - from kefi.config import settings 5 - 6 - engine = create_engine(settings.db_url()) 6 + if TYPE_CHECKING: 7 + from kefi.models.kudos.database import Action 7 8 8 9 9 10 class User(SQLModel, table=True): # type: ignore ··· 33 34 return self.first_name or self.slack_username 34 35 35 36 36 - class Action(SQLModel, table=True): # type: ignore 37 - """Each action a user can perform to give kefis to another user.""" 38 - 39 - id: int | None = Field(default=None, primary_key=True) 40 - keyword: str = Field(sa_column=Column(String(100), unique=True, index=True)) 41 - amount: int 42 - transactions: list["Transaction"] = Relationship(back_populates="action") 43 - # Responses data 44 - header_template: str = "" 45 - message_template: str = "" 46 - context_template: str = "" 47 - image: str | None 48 - text: str | None 49 - 50 - 51 37 class Transaction(SQLModel, table=True): # type: ignore 52 38 """A transaction is a kefi movement, form the system to a user or from a user to 53 39 another. ··· 64 50 65 51 # Action, the reference of the action used to send the transaction 66 52 action_id: int | None = Field(default=None, foreign_key="action.id") 67 - action: Action | None = Relationship(back_populates="transactions") 53 + action: Optional["Action"] = Relationship(back_populates="transactions") 68 54 69 55 # Sender user, if not defined, is a system transaction 70 56 sender_id: int | None = Field(default=None, foreign_key="user.id")
-235
kefi/models/helpers.py
··· 1 - from slack_sdk import WebClient 2 - from sqlmodel import Session, func, or_, select 3 - 4 - from kefi.config import settings 5 - from kefi.constants import Command 6 - from kefi.dependencies import SlashCommandParams 7 - from kefi.models.database import Action, Transaction, User 8 - from kefi.routers.responses import ActionResponse, ResetResponse 9 - from kefi.slack import Slack 10 - 11 - 12 - def get_or_create_from_command( 13 - command: SlashCommandParams, session: Session 14 - ) -> tuple["User", bool]: 15 - """Obtains the balance of the user.""" 16 - query = select(User).filter(User.slack_user_id == command.user_id) 17 - user = session.exec(query).one_or_none() 18 - created = not user 19 - if not user: 20 - user = User(slack_user_id=command.user_id, slack_username=command.user_name) 21 - else: 22 - # Updates the slack username 23 - user.slack_username = command.user_name 24 - session.add(user) 25 - return (user, created) 26 - 27 - 28 - def available_balance(user: User, session: Session) -> int: 29 - """Obtains the available balance of the user, that means the balance not spend from 30 - the monthly received. 31 - """ 32 - query = select(func.sum(Transaction.amount)).filter( # type: ignore 33 - Transaction.user == user, 34 - or_(Transaction.sender == user, Transaction.sender == None), # type: ignore 35 - ) 36 - return session.exec(query).one() or 0 37 - 38 - 39 - def received_balance(user: User, session: Session) -> int: 40 - """Obtains the received balance of the user, that means the balance sent to the 41 - user. 42 - """ 43 - query = select(func.sum(Transaction.amount)).filter( # type: ignore 44 - Transaction.user == user, Transaction.receiver == user 45 - ) 46 - return session.exec(query).one() or 0 47 - 48 - 49 - def send_action( 50 - sender: User, action: Action, receiver: User, message: str, session: Session 51 - ) -> list["Transaction"]: 52 - """Creates the transaction needed to send an action from sender to receiver.""" 53 - # Checks sender wallet 54 - balance = available_balance(user=sender, session=session) 55 - if balance < action.amount: 56 - raise ValueError("The user doesn't have enough balance") 57 - # Creates transactions 58 - sender_transaction = Transaction( 59 - action=action, 60 - amount=-action.amount, 61 - user=sender, 62 - sender=sender, 63 - receiver=receiver, 64 - message=message, 65 - ) 66 - receiver_transaction = Transaction( 67 - action=action, 68 - amount=action.amount, 69 - user=receiver, 70 - sender=sender, 71 - receiver=receiver, 72 - message=message, 73 - ) 74 - session.add(sender_transaction) 75 - session.add(receiver_transaction) 76 - return [sender_transaction, receiver_transaction] 77 - 78 - 79 - def notify_receiver_user_chat_action( 80 - action: Action, sender: User, receiver: User, message: str, channel_id: str 81 - ): 82 - slack = Slack() 83 - action_response = ActionResponse( 84 - sender=sender, 85 - receiver=receiver, 86 - action=action, 87 - message=message, 88 - channel_id=channel_id, 89 - ) 90 - 91 - text = None 92 - if action.text: 93 - text = action.text.format( 94 - sender_name=f"@{sender.slack_username}", 95 - receiver_name=f"@{receiver.slack_username}", 96 - ) 97 - 98 - slack.post_message_user( 99 - receiver.slack_user_id, 100 - text=text, 101 - blocks=action_response.render()["blocks"], 102 - ) 103 - 104 - 105 - def send_admin_amount(receiver: User, amount: int, session: Session) -> "Transaction": 106 - """Creates the transaction needed to send an amount to the receiver.""" 107 - receiver_transaction = Transaction(amount=amount, user=receiver) 108 - session.add(receiver_transaction) 109 - return receiver_transaction 110 - 111 - 112 - def recharge_wallets(amount: int, session: Session) -> None: 113 - """Recharge all the wallet with the given amount.""" 114 - query = select(User) 115 - users = session.exec(query).all() 116 - for user in users: 117 - transaction = Transaction(amount=amount, user=user) 118 - session.add(transaction) 119 - 120 - 121 - def reset_wallets(session: "Session") -> None: 122 - """Reset the wallets of all the users.""" 123 - users = session.exec(select(User)).all() 124 - for user in users: 125 - balance = available_balance(user=user, session=session) 126 - amount = settings.RECHARGE_KEFIS_AMOUNT - balance 127 - transaction = Transaction(amount=amount, user=user) 128 - session.add(transaction) 129 - 130 - 131 - def notify_reset_wallet(session: Session) -> None: 132 - """Sends notifications after the wallet was reset.""" 133 - amount = settings.RECHARGE_KEFIS_AMOUNT 134 - users = session.exec(select(User)).all() 135 - for user in users: 136 - slack = Slack() 137 - response = ResetResponse(amount=amount) 138 - slack.post_message_user(user.slack_user_id, blocks=response.render()["blocks"]) 139 - 140 - 141 - def create_default_actions(session: Session) -> None: 142 - """Creates the default actions if they doesn't exists.""" 143 - actions: list[dict[str, int | str]] = [ 144 - { 145 - "keyword": Command.KUDOS, 146 - "amount": 100, 147 - "header_template": "¡Gracias {receiver_name}!", 148 - "message_template": "Mensaje de {sender_name}:\n _{message}_", 149 - "context_template": "*{sender_name}* le da a *{receiver_name}* {amount} kefis.", 150 - "image": "https://storage.staging.dekaside.com/kefi/static/images/kudos_400.png", 151 - "text": "{sender_name} le da las gracias a {receiver_name}", 152 - }, 153 - { 154 - "keyword": Command.CONGRATS, 155 - "amount": 25, 156 - "header_template": "¡Enhorabuena {receiver_name}!", 157 - "message_template": "Mensaje de {sender_name}:\n _{message}_", 158 - "context_template": "*{sender_name}* le da a *{receiver_name}* {amount} kefis.", 159 - "image": "https://storage.staging.dekaside.com/kefi/static/images/congrats_400.png", 160 - "text": "{sender_name} le da la enhorabuena a {receiver_name}", 161 - }, 162 - { 163 - "keyword": Command.HIGH_FIVE, 164 - "amount": 5, 165 - "header_template": "¡{sender_name} le envía un high five a {receiver_name}!", 166 - "message_template": "_{message}_", 167 - "context_template": "*{sender_name}* le da a *{receiver_name}* {amount} kefis.", 168 - "image": "https://storage.staging.dekaside.com/kefi/static/images/highfive_400.png", 169 - "text": "{sender_name} le envia un high five a {receiver_name}", 170 - }, 171 - ] 172 - for action_data in actions: 173 - action: Action | None = session.exec( 174 - select(Action).filter(Action.keyword == action_data["keyword"]) 175 - ).one_or_none() 176 - if not action: 177 - session.add(Action(**action_data)) 178 - else: 179 - for key, value in action_data.items(): 180 - setattr(action, key, value) 181 - session.add(action) 182 - 183 - 184 - def find_user_by_slack_user_id(slack_user_id: str, session: Session) -> User | None: 185 - """Gets the user using the Slack user id.""" 186 - query = select(User).filter(User.slack_user_id == slack_user_id) 187 - return session.exec(query).one_or_none() 188 - 189 - 190 - def find_user_by_slack_username(slack_username: str, session: Session) -> User | None: 191 - """Gets the user using the Slack username.""" 192 - query = select(User).filter(User.slack_username == slack_username) 193 - return session.exec(query).one_or_none() 194 - 195 - 196 - def get_action(keyword: str, session: Session) -> Action | None: 197 - query = select(Action).filter(Action.keyword == keyword) 198 - return session.exec(query).one_or_none() 199 - 200 - 201 - def create_users(session: Session) -> None: 202 - """Creates all the users in the team.""" 203 - # Gets the users 204 - client = WebClient(token=settings.SLACK_BOT_TOKEN) 205 - response = client.users_list(team_id=settings.SLACK_TEAM_ID) 206 - members = response["members"] 207 - while response["response_metadata"]["next_cursor"]: 208 - response = client.users_list(team_id=settings.SLACK_TEAM_ID) 209 - members += response["members"] 210 - members = list( 211 - filter(lambda user: not user["is_bot"] and not user["deleted"], members) 212 - ) 213 - # Create or updates the users 214 - for member in members: 215 - query = select(User).filter(User.slack_user_id == member["id"]) 216 - user = session.exec(query).one_or_none() 217 - if not user: 218 - user = User( 219 - slack_user_id=member["id"], 220 - slack_username=member["name"], 221 - first_name=member["profile"].get("first_name", ""), 222 - last_name=member["profile"].get("last_name", ""), 223 - is_admin=member["is_admin"], 224 - ) 225 - else: 226 - user.slack_username = member["name"] 227 - user.is_admin = member["is_admin"] 228 - user.first_name = member["profile"].get("first_name", "") 229 - user.last_name = member["profile"].get("last_name", "") 230 - session.add(user) 231 - 232 - 233 - def get_all_users(session: Session) -> list["User"]: 234 - """Gets the complete list of users.""" 235 - return session.exec(select(User)).all()
kefi/models/kudos/__init__.py

This is a binary file and will not be displayed.

+22
kefi/models/kudos/database.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.database import Transaction 8 + 9 + 10 + class Action(SQLModel, table=True): # type: ignore 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
+114
kefi/models/kudos/helpers.py
··· 1 + from sqlmodel import Session, select 2 + 3 + from kefi.constants import Command 4 + from kefi.models.core.database import Transaction, User 5 + from kefi.models.core.helpers import available_balance 6 + from kefi.models.kudos.database import Action 7 + from kefi.routers.responses import ActionResponse 8 + from kefi.slack import Slack 9 + 10 + 11 + def create_default_actions(session: Session) -> None: 12 + """Creates the default actions if they doesn't exists.""" 13 + actions: list[dict[str, int | str]] = [ 14 + { 15 + "keyword": Command.KUDOS, 16 + "amount": 100, 17 + "header_template": "¡Gracias {receiver_name}!", 18 + "message_template": "Mensaje de {sender_name}:\n _{message}_", 19 + "context_template": "*{sender_name}* le da a *{receiver_name}* {amount} kefis.", 20 + "image": "https://storage.staging.dekaside.com/kefi/static/images/kudos_400.png", 21 + "text": "{sender_name} le da las gracias a {receiver_name}", 22 + }, 23 + { 24 + "keyword": Command.CONGRATS, 25 + "amount": 25, 26 + "header_template": "¡Enhorabuena {receiver_name}!", 27 + "message_template": "Mensaje de {sender_name}:\n _{message}_", 28 + "context_template": "*{sender_name}* le da a *{receiver_name}* {amount} kefis.", 29 + "image": "https://storage.staging.dekaside.com/kefi/static/images/congrats_400.png", 30 + "text": "{sender_name} le da la enhorabuena a {receiver_name}", 31 + }, 32 + { 33 + "keyword": Command.HIGH_FIVE, 34 + "amount": 5, 35 + "header_template": "¡{sender_name} le envía un high five a {receiver_name}!", 36 + "message_template": "_{message}_", 37 + "context_template": "*{sender_name}* le da a *{receiver_name}* {amount} kefis.", 38 + "image": "https://storage.staging.dekaside.com/kefi/static/images/highfive_400.png", 39 + "text": "{sender_name} le envia un high five a {receiver_name}", 40 + }, 41 + ] 42 + for action_data in actions: 43 + action: Action | None = session.exec( 44 + select(Action).filter(Action.keyword == action_data["keyword"]) 45 + ).one_or_none() 46 + if not action: 47 + session.add(Action(**action_data)) 48 + else: 49 + for key, value in action_data.items(): 50 + setattr(action, key, value) 51 + session.add(action) 52 + 53 + 54 + def send_action( 55 + sender: User, action: Action, receiver: User, message: str, session: Session 56 + ) -> list["Transaction"]: 57 + """Creates the transaction needed to send an action from sender to receiver.""" 58 + # Checks sender wallet 59 + balance = available_balance(user=sender, session=session) 60 + if balance < action.amount: 61 + raise ValueError("The user doesn't have enough balance") 62 + # Creates transactions 63 + sender_transaction = Transaction( 64 + action=action, 65 + amount=-action.amount, 66 + user=sender, 67 + sender=sender, 68 + receiver=receiver, 69 + message=message, 70 + ) 71 + receiver_transaction = Transaction( 72 + action=action, 73 + amount=action.amount, 74 + user=receiver, 75 + sender=sender, 76 + receiver=receiver, 77 + message=message, 78 + ) 79 + session.add(sender_transaction) 80 + session.add(receiver_transaction) 81 + return [sender_transaction, receiver_transaction] 82 + 83 + 84 + def notify_receiver_user_chat_action( 85 + action: Action, sender: User, receiver: User, message: str, channel_id: str 86 + ): 87 + """Sends a notification that someone has send kefis to you.""" 88 + slack = Slack() 89 + action_response = ActionResponse( 90 + sender=sender, 91 + receiver=receiver, 92 + action=action, 93 + message=message, 94 + channel_id=channel_id, 95 + ) 96 + 97 + text = None 98 + if action.text: 99 + text = action.text.format( 100 + sender_name=f"@{sender.slack_username}", 101 + receiver_name=f"@{receiver.slack_username}", 102 + ) 103 + 104 + slack.post_message_user( 105 + receiver.slack_user_id, 106 + text=text, 107 + blocks=action_response.render()["blocks"], 108 + ) 109 + 110 + 111 + def get_action(keyword: str, session: Session) -> Action | None: 112 + """Gets an action using the keyword.""" 113 + query = select(Action).filter(Action.keyword == keyword) 114 + return session.exec(query).one_or_none()
+6 -4
kefi/routers/helpers.py
··· 5 5 6 6 from kefi.constants import Command 7 7 from kefi.dependencies import SlashCommandParams 8 - from kefi.models.helpers import ( 8 + from kefi.models.core.helpers import ( 9 9 available_balance, 10 10 find_user_by_slack_user_id, 11 - get_action, 12 11 get_all_users, 13 12 get_or_create_from_command, 14 - notify_receiver_user_chat_action, 15 13 received_balance, 16 - send_action, 17 14 send_admin_amount, 15 + ) 16 + from kefi.models.kudos.helpers import ( 17 + get_action, 18 + notify_receiver_user_chat_action, 19 + send_action, 18 20 ) 19 21 from kefi.routers.responses import ( 20 22 ActionResponse,
+2 -1
kefi/routers/responses.py
··· 1 - from kefi.models.database import Action, User 1 + from kefi.models.core.database import User 2 + from kefi.models.kudos.database import Action 2 3 from kefi.models.outputs import ( 3 4 Context, 4 5 Header,
+1 -1
kefi/tasks/main.py
··· 3 3 from sqlmodel import Session 4 4 5 5 from kefi.config import settings 6 - from kefi.models.database import engine 6 + from kefi.models import engine 7 7 from kefi.tasks.wallets import recharge_wallets_task 8 8 9 9
+1 -1
kefi/tasks/wallets.py
··· 1 - from kefi.models.helpers import notify_reset_wallet, reset_wallets 1 + from kefi.models.core.helpers import notify_reset_wallet, reset_wallets 2 2 3 3 4 4 async def recharge_wallets_task(ctx):
+1 -1
kefi/tests/conftest.py
··· 9 9 from kefi.config import settings 10 10 from kefi.dependencies import get_session 11 11 from kefi.main import app 12 - from kefi.models.helpers import create_default_actions 12 + from kefi.models.kudos.helpers import create_default_actions 13 13 14 14 15 15 def run_migrations(url):
+2 -2
kefi/tests/test_commands.py
··· 4 4 from sqlmodel import Session 5 5 from sqlmodel.sql.expression import select 6 6 7 - from kefi.models.database import Transaction, User 8 - from kefi.models.helpers import available_balance, reset_wallets 7 + from kefi.models.core.database import Transaction, User 8 + from kefi.models.core.helpers import available_balance, reset_wallets 9 9 from kefi.routers.helpers import Command 10 10 from kefi.tests.helpers import generate_command 11 11
+4 -3
kefi/tests/test_models.py
··· 1 1 from sqlmodel import Session 2 2 3 3 from kefi.config import settings 4 - from kefi.models.database import Action, Transaction, User 5 - from kefi.models.helpers import ( 4 + from kefi.models.core.database import Transaction, User 5 + from kefi.models.core.helpers import ( 6 6 available_balance, 7 7 received_balance, 8 8 recharge_wallets, 9 9 reset_wallets, 10 - send_action, 11 10 ) 11 + from kefi.models.kudos.database import Action 12 + from kefi.models.kudos.helpers import send_action 12 13 13 14 14 15 def test_available_balance(session: Session):
+3 -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.database import Action, User 7 - from kefi.models.helpers import create_default_actions 6 + from kefi.models.core.database import User 7 + from kefi.models.kudos.database import Action 8 + from kefi.models.kudos.helpers import create_default_actions 8 9 from kefi.routers.responses import ActionResponse 9 10 10 11
+3 -1
manage.py
··· 13 13 from sqlmodel import Session, select 14 14 15 15 from kefi.config import settings 16 - from kefi.models.database import Action, Transaction, User, engine 16 + from kefi.models import engine 17 + from kefi.models.core.database import Transaction, User 18 + from kefi.models.kudos.database import Action 17 19 18 20 session = Session(engine) 19 21 return {