Compare commits

...

6 Commits

5 changed files with 88 additions and 20 deletions

View File

@ -6,13 +6,15 @@ from werkzeug.exceptions import NotFound
@click.command(no_args_is_help=True) @click.command(no_args_is_help=True)
@click.option("--sync", is_flag=True, default=False, help="Synchronize users from LDAP -> database") @click.option("--sync", is_flag=True, default=False, help="Synchronize users from LDAP -> database")
@click.option("--sync-ldap", is_flag=True, default=False, help="Synchronize users from database -> LDAP")
@with_appcontext @with_appcontext
@click.pass_context @click.pass_context
def ldap(ctx, sync): def ldap(ctx, sync, sync_ldap):
"""Tools for the LDAP authentification""" """Tools for the LDAP authentification"""
if sync:
from flaschengeist.controller import userController from flaschengeist.controller import userController
from flaschengeist.plugins.auth_ldap import AuthLDAP from flaschengeist.plugins.auth_ldap import AuthLDAP
if sync:
click.echo("Synchronizing users from LDAP -> database")
from ldap3 import SUBTREE from ldap3 import SUBTREE
from flaschengeist.models import User from flaschengeist.models import User
from flaschengeist.database import db from flaschengeist.database import db
@ -33,3 +35,13 @@ def ldap(ctx, sync):
user = User(userid=uid) user = User(userid=uid)
db.session.add(user) db.session.add(user)
userController.update_user(user, auth_ldap) userController.update_user(user, auth_ldap)
if sync_ldap:
click.echo("Synchronizing users from database -> LDAP")
auth_ldap: AuthLDAP = current_app.config.get("FG_PLUGINS").get("auth_ldap")
if auth_ldap is None or not isinstance(auth_ldap, AuthLDAP):
ctx.fail("auth_ldap plugin not found or not enabled!")
users = userController.get_users()
for user in users:
userController.update_user(user, auth_ldap)

View File

@ -36,7 +36,7 @@ def get_limit(user: User) -> float:
def get_balance(user, start: datetime = None, end: datetime = None): def get_balance(user, start: datetime = None, end: datetime = None):
query = db.session.query(func.sum(Transaction.amount)) query = db.session.query(func.sum(Transaction._amount))
if start: if start:
query = query.filter(start <= Transaction.time) query = query.filter(start <= Transaction.time)
if end: if end:
@ -48,7 +48,13 @@ def get_balance(user, start: datetime = None, end: datetime = None):
def get_balances( def get_balances(
start: datetime = None, end: datetime = None, limit=None, offset=None, descending=None, sortBy=None, _filter=None start: datetime = None,
end: datetime = None,
limit=None,
offset=None,
descending=None,
sortBy=None,
_filter=None,
): ):
logger.debug( logger.debug(
f"get_balances(start={start}, end={end}, limit={limit}, offset={offset}, descending={descending}, sortBy={sortBy}, _filter={_filter})" f"get_balances(start={start}, end={end}, limit={limit}, offset={offset}, descending={descending}, sortBy={sortBy}, _filter={_filter})"
@ -56,7 +62,11 @@ def get_balances(
class _User(User): class _User(User):
_debit = db.relationship(Transaction, back_populates="sender_", foreign_keys=[Transaction._sender_id]) _debit = db.relationship(Transaction, back_populates="sender_", foreign_keys=[Transaction._sender_id])
_credit = db.relationship(Transaction, back_populates="receiver_", foreign_keys=[Transaction._receiver_id]) _credit = db.relationship(
Transaction,
back_populates="receiver_",
foreign_keys=[Transaction._receiver_id],
)
@hybrid_property @hybrid_property
def debit(self): def debit(self):
@ -65,8 +75,8 @@ def get_balances(
@debit.expression @debit.expression
def debit(cls): def debit(cls):
a = ( a = (
db.select(func.sum(Transaction.amount)) db.select(func.sum(Transaction._amount))
.where(cls.id_ == Transaction._sender_id, Transaction.amount) .where(cls.id_ == Transaction._sender_id, Transaction._amount)
.scalar_subquery() .scalar_subquery()
) )
return case([(a, a)], else_=0) return case([(a, a)], else_=0)
@ -78,8 +88,8 @@ def get_balances(
@credit.expression @credit.expression
def credit(cls): def credit(cls):
b = ( b = (
db.select(func.sum(Transaction.amount)) db.select(func.sum(Transaction._amount))
.where(cls.id_ == Transaction._receiver_id, Transaction.amount) .where(cls.id_ == Transaction._receiver_id, Transaction._amount)
.scalar_subquery() .scalar_subquery()
) )
return case([(b, b)], else_=0) return case([(b, b)], else_=0)
@ -92,7 +102,12 @@ def get_balances(
def limit(cls): def limit(cls):
return ( return (
db.select(_UserAttribute.value) db.select(_UserAttribute.value)
.where(and_(cls.id_ == _UserAttribute.user, _UserAttribute.name == "balance_limit")) .where(
and_(
cls.id_ == _UserAttribute.user,
_UserAttribute.name == "balance_limit",
)
)
.scalar_subquery() .scalar_subquery()
) )
@ -127,14 +142,25 @@ def get_balances(
if _filter: if _filter:
query = query.filter( query = query.filter(
or_(_User.firstname.ilike(f"%{_filter.lower()}%"), _User.lastname.ilike(f"%{_filter.lower()}%")) or_(
_User.firstname.ilike(f"%{_filter.lower()}%"),
_User.lastname.ilike(f"%{_filter.lower()}%"),
)
) )
if sortBy == "balance": if sortBy == "balance":
if descending: if descending:
query = query.order_by((_User.credit - _User.debit).desc(), _User.lastname.asc(), _User.firstname.asc()) query = query.order_by(
(_User.credit - _User.debit).desc(),
_User.lastname.asc(),
_User.firstname.asc(),
)
else: else:
query = query.order_by((_User.credit - _User.debit).asc(), _User.lastname.asc(), _User.firstname.asc()) query = query.order_by(
(_User.credit - _User.debit).asc(),
_User.lastname.asc(),
_User.firstname.asc(),
)
elif sortBy == "limit": elif sortBy == "limit":
if descending: if descending:
query = query.order_by(_User.limit.desc(), User.lastname.asc(), User.firstname.asc()) query = query.order_by(_User.limit.desc(), User.lastname.asc(), User.firstname.asc())
@ -209,7 +235,11 @@ def send(sender: User, receiver, amount: float, author: User):
BalancePlugin.getPlugin().notify( BalancePlugin.getPlugin().notify(
sender, sender,
"Neue Transaktion", "Neue Transaktion",
{"type": NotifyType.SUB_FROM, "author_id": author.userid, "amount": amount}, {
"type": NotifyType.SUB_FROM,
"author_id": author.userid,
"amount": amount,
},
) )
if receiver is not None and receiver.id_ != author.id_: if receiver is not None and receiver.id_ != author.id_:
if sender is not None: if sender is not None:
@ -226,7 +256,11 @@ def send(sender: User, receiver, amount: float, author: User):
BalancePlugin.getPlugin().notify( BalancePlugin.getPlugin().notify(
receiver, receiver,
"Neue Transaktion", "Neue Transaktion",
{"type": NotifyType.ADD_FROM, "author_id": author.userid, "amount": amount}, {
"type": NotifyType.ADD_FROM,
"author_id": author.userid,
"amount": amount,
},
) )
return transaction return transaction

View File

@ -1,7 +1,9 @@
from datetime import datetime from datetime import datetime
from typing import Optional from typing import Optional
from sqlalchemy.ext.hybrid import hybrid_property from sqlalchemy.ext.hybrid import hybrid_property
from math import floor
from flaschengeist import logger
from flaschengeist.database import db from flaschengeist.database import db
from flaschengeist.models.user import User from flaschengeist.models.user import User
from flaschengeist.models import ModelSerializeMixin, UtcDateTime, Serial from flaschengeist.models import ModelSerializeMixin, UtcDateTime, Serial
@ -18,8 +20,9 @@ class Transaction(db.Model, ModelSerializeMixin):
# Public and exported member # Public and exported member
id: int = db.Column("id", Serial, primary_key=True) id: int = db.Column("id", Serial, primary_key=True)
time: datetime = db.Column(UtcDateTime, nullable=False, default=UtcDateTime.current_utc) time: datetime = db.Column(UtcDateTime, nullable=False, default=UtcDateTime.current_utc)
amount: float = db.Column(db.Numeric(precision=5, scale=2, asdecimal=False), nullable=False) _amount: float = db.Column("amount", db.Numeric(precision=5, scale=2, asdecimal=False), nullable=False)
reversal_id: Optional[int] = db.Column(Serial, db.ForeignKey("balance_transaction.id")) reversal_id: Optional[int] = db.Column(Serial, db.ForeignKey("balance_transaction.id"))
amount: float
# Dummy properties used for JSON serialization (userid instead of full user) # Dummy properties used for JSON serialization (userid instead of full user)
author_id: Optional[str] = None author_id: Optional[str] = None
@ -56,3 +59,14 @@ class Transaction(db.Model, ModelSerializeMixin):
@property @property
def original_id(self): def original_id(self):
return self.original_.id if self.original_ else None return self.original_.id if self.original_ else None
@property
def amount(self):
return self._amount
@amount.setter
def amount(self, value):
self._amount = floor(value * 100) / 100
def __repr__(self):
return f"<Transaction {self.id} {self.amount} {self.time} {self.sender_id} {self.receiver_id} {self.author_id}>"

View File

@ -1,4 +1,5 @@
from datetime import datetime, timezone from datetime import datetime, timezone
from logging import log
from werkzeug.exceptions import Forbidden, BadRequest from werkzeug.exceptions import Forbidden, BadRequest
from flask import Blueprint, request, jsonify from flask import Blueprint, request, jsonify
@ -163,6 +164,7 @@ def get_balance(userid, current_session: Session):
end = datetime.now(tz=timezone.utc) end = datetime.now(tz=timezone.utc)
balance = balance_controller.get_balance(user, start, end) balance = balance_controller.get_balance(user, start, end)
logger.debug(f"Balance of {user.userid} from {start} to {end}: {balance}")
return {"credit": balance[0], "debit": balance[1], "balance": balance[2]} return {"credit": balance[0], "debit": balance[1], "balance": balance[2]}
@ -224,6 +226,7 @@ def get_transactions(userid, current_session: Session):
show_cancelled=show_cancelled, show_cancelled=show_cancelled,
descending=descending, descending=descending,
) )
logger.debug(f"transactions: {transactions}")
return {"transactions": transactions, "count": count} return {"transactions": transactions, "count": count}
@ -321,7 +324,11 @@ def get_balances(current_session: Session):
_filter = request.args.get("filter", None, type=str) _filter = request.args.get("filter", None, type=str)
logger.debug(f"request.args: {request.args}") logger.debug(f"request.args: {request.args}")
balances, count = balance_controller.get_balances( balances, count = balance_controller.get_balances(
limit=limit, offset=offset, descending=descending, sortBy=sortBy, _filter=_filter limit=limit,
offset=offset,
descending=descending,
sortBy=sortBy,
_filter=_filter,
) )
return jsonify( return jsonify(
{ {

View File

@ -1,6 +1,6 @@
[metadata] [metadata]
license = MIT license = MIT
version = 2.0.0.dev1 version = 2.0.0
name = flaschengeist name = flaschengeist
author = Tim Gröger author = Tim Gröger
author_email = flaschengeist@wu5.de author_email = flaschengeist@wu5.de
@ -22,7 +22,8 @@ include_package_data = True
python_requires = >=3.10 python_requires = >=3.10
packages = find: packages = find:
install_requires = install_requires =
Flask>=2.2.2, <2.3 #Flask>=2.2.2, <2.3
Flask>=2.2.2, <2.9
Pillow>=9.2 Pillow>=9.2
flask_cors flask_cors
flask_migrate>=3.1.0 flask_migrate>=3.1.0