Skip to content

Commit

Permalink
Merge pull request #61 from VaquitApp/develop
Browse files Browse the repository at this point in the history
Merge develop (Sprint 6)
  • Loading branch information
MegaRedHand authored Jun 13, 2024
2 parents a99b5ab + 0ba4662 commit 9f4320e
Show file tree
Hide file tree
Showing 11 changed files with 1,388 additions and 487 deletions.
2 changes: 2 additions & 0 deletions .flake8
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[flake8]
max-line-length = 88
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -176,3 +176,4 @@ pyrightconfig.json
# End of https://www.toptal.com/developers/gitignore/api/python

*.db
.vscode
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ lint:

test:
rm test.db 2> /dev/null || true
DB_NAME="./test.db" poetry run pytest -svv .
DB_NAME="./test.db" poetry run pytest -svvx --ff .

CONTAINER_NAME="postgres_test_db"

Expand Down
667 changes: 370 additions & 297 deletions poetry.lock

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ fastapi = "^0.111.0"
sqlalchemy = "^2.0.30"
uvicorn = "^0.29.0"
flake8 = "^7.0.0"
psycopg2 = "^2.9.9"
psycopg2-binary = "^2.9.9"
python-jose = {extras = ["cryptography"], version = "^3.3.0"}
sib-api-v3-sdk = "^7.6.0"

Expand Down
192 changes: 178 additions & 14 deletions src/crud.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from sqlalchemy import delete, select
from typing import List, Optional
from sqlalchemy import select
from sqlalchemy.orm import Session
from uuid import UUID

Expand All @@ -14,7 +15,7 @@ def get_user_by_id(db: Session, id: int):
return db.query(models.User).filter(models.User.id == id).first()


def get_user_by_email(db: Session, email: str) -> models.User:
def get_user_by_email(db: Session, email: str) -> Optional[models.User]:
return db.query(models.User).filter(models.User.email == email).first()


Expand Down Expand Up @@ -87,8 +88,8 @@ def create_group(db: Session, group: schemas.GroupCreate, user_id: int):

# Add the owner to the group members
db_user = get_user_by_id(db, user_id)
db_user.groups.add(db_group)

db_group.members.add(db_user)
db.commit()
db.refresh(db_group)
return db_group
Expand Down Expand Up @@ -125,45 +126,135 @@ def update_group_status(db: Session, group: models.Group, status: bool):
return group


def add_user_to_group(db: Session, group: models.Group, user_id: int):
user = get_user_by_id(db, user_id)
def add_user_to_group(db: Session, user: models.User, group: models.Group):
group.members.add(user)
db.commit()
db.refresh(group)
return group


################################################
# SPENDINGS
# ALL SPENDINGS
################################################

def get_all_spendings_by_group_id(db: Session, group_id: int):

unique_spendings = db.query(models.UniqueSpending).filter(models.UniqueSpending.group_id == group_id).limit(100).all()
installment_spendings = db.query(models.InstallmentSpending).filter(models.InstallmentSpending.group_id == group_id).limit(100).all()
recurring_spendings = db.query(models.RecurringSpending).filter(models.RecurringSpending.group_id == group_id).limit(100).all()

def create_spending(db: Session, spending: schemas.SpendingCreate, user_id: int):
db_spending = models.Spending(owner_id=user_id, **dict(spending))
unique_spendings = list(map(lambda spending: {**spending.__dict__, "type": "unique_spending"}, unique_spendings))
installment_spendings = list(map(lambda spending: {**spending.__dict__, "type": "installment_spending"}, installment_spendings))
recurring_spendings = list(map(lambda spending: {**spending.__dict__, "type": "recurring_spending"}, recurring_spendings))

return unique_spendings + installment_spendings + recurring_spendings

################################################
# UNIQUE SPENDINGS
################################################

def create_unique_spending(db: Session, spending: schemas.UniqueSpendingCreate, user_id: int):
db_spending = models.UniqueSpending(owner_id=user_id, **dict(spending))
db.add(db_spending)
db.commit()
db.refresh(db_spending)
update_balances_from_spending(db, db_spending)
db.refresh(db_spending)
return db_spending


def get_spendings_by_group_id(db: Session, group_id: int):
def get_unique_spendings_by_group_id(db: Session, group_id: int):
return (
db.query(models.Spending)
.filter(models.Spending.group_id == group_id)
db.query(models.UniqueSpending)
.filter(models.UniqueSpending.group_id == group_id)
.limit(100)
.all()
)


def get_spendings_by_category(db: Session, category_id: int):
################################################
# INSTALLMENT SPENDINGS
################################################


def create_installment_spending(db: Session, spending: schemas.InstallmentSpendingCreate, user_id: int, current_installment:int):
db_spending = models.InstallmentSpending(owner_id=user_id, current_installment=current_installment,**dict(spending))
db.add(db_spending)
db.commit()
db.refresh(db_spending)
update_balances_from_spending(db, db_spending)
db.refresh(db_spending)
return db_spending


def get_installment_spendings_by_group_id(db: Session, group_id: int):
return (
db.query(models.Spending)
.filter(models.Spending.category_id == category_id)
db.query(models.InstallmentSpending)
.filter(models.InstallmentSpending.group_id == group_id)
.limit(100)
.all()
)


################################################
# RECURRING SPENDINGS
################################################


def create_recurring_spending(db: Session, spending: schemas.RecurringSpendingBase, user_id: int):
db_spending = models.RecurringSpending(owner_id=user_id, **dict(spending))
db.add(db_spending)
db.commit()
db.refresh(db_spending)
update_balances_from_spending(db, db_spending)
db.refresh(db_spending)
return db_spending


def get_recurring_spendings_by_id(db: Session, recurring_spendig_id: int):
return db.query(models.RecurringSpending).filter(models.RecurringSpending.id == recurring_spendig_id).first()


def get_recurring_spendings_by_group_id(db: Session, group_id: int):
return (
db.query(models.RecurringSpending)
.filter(models.RecurringSpending.group_id == group_id)
.limit(100)
.all()
)


def put_recurring_spendings(db: Session, db_recurring_spending: models.RecurringSpending, put_recurring_spending: schemas.RecurringSpendingPut):
db_recurring_spending.amount = put_recurring_spending.amount
db_recurring_spending.description = put_recurring_spending.description
db_recurring_spending.category_id = put_recurring_spending.categiry_id
db.commit()
db.refresh(db_recurring_spending)
return db_recurring_spending


################################################
# PAYMENTS
################################################


def create_payment(db: Session, payment: schemas.PaymentCreate):
db_payment = models.Payment(**dict(payment))
update_balances_from_payment(db, db_payment)
db.add(db_payment)
db.commit()
db.refresh(db_payment)
return db_payment


def get_payments_by_group_id(db: Session, group_id: int):
return (
db.query(models.Payment)
.filter(models.Payment.group_id == group_id)
.limit(100)
.all()
)

################################################
# BUDGETS
################################################
Expand Down Expand Up @@ -233,3 +324,76 @@ def update_invite_status(
db.commit()
db.refresh(db_invite)
return db_invite


################################################
# REMINDERS
################################################


def create_payment_reminder(
db: Session, payment_reminder: schemas.PaymentReminderCreate, sender_id: int
):
db_reminder = models.PaymentReminder(
sender_id=sender_id,
receiver_id=payment_reminder.receiver_id,
group_id=payment_reminder.group_id,
)

if payment_reminder.message is not None:
db_reminder.message = payment_reminder.message

db.add(db_reminder)
db.commit()
db.refresh(db_reminder)
return db_reminder


################################################
# BALANCES
################################################


def update_balances_from_spending(db: Session, spending: models.UniqueSpending):
group = get_group_by_id(db, spending.group_id)
balances = sorted(
get_balances_by_group_id(db, spending.group_id), key=lambda x: x.user_id
)
members = sorted(group.members, key=lambda x: x.id)
# TODO: implement division strategy
# TODO: this truncates decimals
amount_per_member = spending.amount // len(members)
for user, balance in zip(members, balances):
amount = -amount_per_member

if spending.owner_id == user.id:
amount += spending.amount

balance.current_balance += amount

db.commit()


def update_balances_from_payment(db: Session, payment: models.Payment):
balances = get_balances_by_group_id(db, payment.group_id)

# Update payer balance
payer = get_user_by_id(db, payment.from_id)
payer_balance = next(filter(lambda x: x.user_id == payer.id, balances))
payer_balance.current_balance += payment.amount

# Update payee balance
payee = get_user_by_id(db, payment.to_id)
payee_balance = next(filter(lambda x: x.user_id == payee.id, balances))
payee_balance.current_balance -= payment.amount

db.commit()


def get_balances_by_group_id(db: Session, group_id: int) -> List[models.Balance]:
return (
db.query(models.Balance)
.filter(models.Balance.group_id == group_id)
.limit(100)
.all()
)
53 changes: 48 additions & 5 deletions src/mail.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,34 @@
from datetime import datetime
import os
from logging import info, error, warning
from typing import Optional
import sib_api_v3_sdk as sdk
from sib_api_v3_sdk.rest import ApiException

from src import schemas

BASE_URL = os.environ.get("BASE_URL", "http://localhost:3000")
API_KEY = os.environ.get("EMAIL_API_KEY")
TEMPLATE_ID = 1

INVITE_TEMPLATE_ID = 1
REMINDER_TEMPLATE_ID = 2
DEFAULT_REMINDER = "No demores muucho con la deuda! :D"


class MailSender(ABC):
@abstractmethod
def send(self, sender: str, receiver: str, group_name: str) -> bool:
def send_invite(self, sender: str, receiver: str, group_name: str) -> bool:
pass

@abstractmethod
def send_reminder(
self, sender: str, receiver: str, group_id: int, message: Optional[str]
) -> bool:
pass


class ProdMailSender(MailSender):
def send(
def send_invite(
self, sender: str, receiver: str, group: schemas.Group, token: str
) -> bool:
configuration = sdk.Configuration()
Expand All @@ -35,7 +45,35 @@ def send(
"join_link": f"{BASE_URL}/invites/accept/{token}",
}

email = sdk.SendSmtpEmail(to=to, template_id=TEMPLATE_ID, params=params)
email = sdk.SendSmtpEmail(to=to, template_id=INVITE_TEMPLATE_ID, params=params)

try:
response = api_instance.send_transac_email(email)
info(response)
return True
except ApiException as e:
error(f"Failed to send email with error: {e}")
return False

def send_reminder(
self, sender: str, receiver: str, group: schemas.Group, message: Optional[str]
) -> bool:
configuration = sdk.Configuration()
configuration.api_key["api-key"] = API_KEY

api_instance = sdk.TransactionalEmailsApi(sdk.ApiClient(configuration))

to = [{"email": receiver}]
params = {
"sender": sender,
"message": DEFAULT_REMINDER if message is None else message,
"landing_page": f"{BASE_URL}",
"group_name": group.name,
}

email = sdk.SendSmtpEmail(
to=to, template_id=REMINDER_TEMPLATE_ID, params=params
)

try:
response = api_instance.send_transac_email(email)
Expand All @@ -47,11 +85,16 @@ def send(


class LocalMailSender(MailSender):
def send(
def send_invite(
self, sender: str, receiver: str, group: schemas.Group, token: str
) -> bool:
return True

def send_reminder(
self, sender: str, receiver: str, group: schemas.Group, message: Optional[str]
) -> bool:
return True


if API_KEY is not None:
mail_service = ProdMailSender()
Expand Down
Loading

0 comments on commit 9f4320e

Please sign in to comment.