diff --git a/files/assets/css/main.css b/files/assets/css/main.css
index 7fabd8d49..ffa94348b 100644
--- a/files/assets/css/main.css
+++ b/files/assets/css/main.css
@@ -7758,3 +7758,11 @@ img[alpha]:not([alt*="#"]) {
td[data-time] {
white-space: pre;
}
+
+.bg-green {
+ background-color: #0a6936 !important;
+}
+
+.bg-red {
+ background-color: #71000b !important;
+}
diff --git a/files/assets/images/WPD/coins.webp b/files/assets/images/coins.webp
similarity index 100%
rename from files/assets/images/WPD/coins.webp
rename to files/assets/images/coins.webp
diff --git a/files/assets/images/rDrama/coins.webp b/files/assets/images/rDrama/coins.webp
deleted file mode 100644
index b57f5ba8b..000000000
Binary files a/files/assets/images/rDrama/coins.webp and /dev/null differ
diff --git a/files/classes/__init__.py b/files/classes/__init__.py
index 88f8ab9fc..730f08481 100644
--- a/files/classes/__init__.py
+++ b/files/classes/__init__.py
@@ -34,6 +34,7 @@ from .push_subscriptions import *
from .group import *
from .group_membership import *
from .orgy import *
+from .currency_logs import *
if FEATURES['IP_LOGGING']:
from .ip_logs import *
diff --git a/files/classes/comment.py b/files/classes/comment.py
index 63359f393..de25267d0 100644
--- a/files/classes/comment.py
+++ b/files/classes/comment.py
@@ -324,7 +324,7 @@ class Comment(Base):
@property
@lazy
def textlink(self):
- return f"[this comment]({self.shortlink})"
+ return f'this comment'
@property
@lazy
diff --git a/files/classes/currency_logs.py b/files/classes/currency_logs.py
new file mode 100644
index 000000000..50c538c29
--- /dev/null
+++ b/files/classes/currency_logs.py
@@ -0,0 +1,29 @@
+import time
+from sqlalchemy import Column, ForeignKey
+from sqlalchemy.sql.sqltypes import *
+
+from files.classes import Base
+from files.helpers.lazy import lazy
+from files.helpers.sorting_and_time import make_age_string
+
+class CurrencyLog(Base):
+ __tablename__ = "currency_logs"
+ id = Column(Integer, primary_key=True)
+ user_id = Column(Integer, ForeignKey("users.id"))
+ created_utc = Column(Integer)
+ currency = Column(String)
+ amount = Column(Integer)
+ reason = Column(String)
+ balance = Column(Integer)
+
+ def __init__(self, *args, **kwargs):
+ if "created_utc" not in kwargs: kwargs["created_utc"] = int(time.time())
+ super().__init__(*args, **kwargs)
+
+ def __repr__(self):
+ return f"<{self.__class__.__name__}(id={self.id})>"
+
+ @property
+ @lazy
+ def age_string(self):
+ return make_age_string(self.created_utc)
diff --git a/files/classes/post.py b/files/classes/post.py
index a67b37a52..5b02bc8d3 100644
--- a/files/classes/post.py
+++ b/files/classes/post.py
@@ -156,7 +156,7 @@ class Post(Base):
@property
@lazy
def textlink(self):
- return f"[{self.title}]({self.shortlink})"
+ return f'{self.title}'
@property
@lazy
diff --git a/files/classes/user.py b/files/classes/user.py
index 68276b19a..f2e2112ac 100644
--- a/files/classes/user.py
+++ b/files/classes/user.py
@@ -14,6 +14,7 @@ from files.classes import Base
from files.classes.casino_game import CasinoGame
from files.classes.group import *
from files.classes.hole import Hole
+from files.classes.currency_logs import CurrencyLog
from files.helpers.config.const import *
from files.helpers.config.modaction_types import *
from files.helpers.config.awards import AWARDS_ENABLED, HOUSE_AWARDS
@@ -213,7 +214,7 @@ class User(Base):
def __repr__(self):
return f"<{self.__class__.__name__}(id={self.id}, username={self.username})>"
- def pay_account(self, currency, amount):
+ def pay_account(self, currency, amount, reason=None):
if self.id in {AUTOJANNY_ID, LONGPOSTBOT_ID, ZOZBOT_ID}:
return
@@ -238,7 +239,20 @@ class User(Base):
else:
user_query.update({ User.marseybux: User.marseybux + amount })
- def charge_account(self, currency, amount, **kwargs):
+ if reason and amount:
+ currency_log = CurrencyLog(
+ user_id=self.id,
+ currency=currency,
+ amount=amount,
+ reason=reason,
+ )
+ g.db.add(currency_log)
+ if currency == 'coins':
+ currency_log.balance = self.coins
+ else:
+ currency_log.balance = self.marseybux
+
+ def charge_account(self, currency, amount, reason=None, **kwargs):
if self.admin_level >= PERMS['INFINITE_CURRENCY']:
return (True, amount)
@@ -256,12 +270,14 @@ class User(Base):
user_query.update({ User.coins: User.coins - amount })
succeeded = True
charged_coins = amount
+ logs = ('coins', amount)
elif currency == 'marseybux':
account_balance = self.marseybux
if not should_check_balance or account_balance >= amount:
user_query.update({ User.marseybux: User.marseybux - amount })
succeeded = True
+ logs = ('marseybux', amount)
elif currency == 'coins/marseybux':
if self.marseybux >= amount:
subtracted_mbux = amount
@@ -278,9 +294,24 @@ class User(Base):
})
succeeded = True
charged_coins = subtracted_coins
+ logs = (('coins', subtracted_coins), ('marseybux', subtracted_mbux))
if succeeded:
g.db.add(self)
+ if reason:
+ for currency, amount in logs:
+ if not amount: continue
+ currency_log = CurrencyLog(
+ user_id=self.id,
+ currency=currency,
+ amount=-amount,
+ reason=reason,
+ )
+ g.db.add(currency_log)
+ if currency == 'coins':
+ currency_log.balance = self.coins
+ else:
+ currency_log.balance = self.marseybux
return (succeeded, charged_coins)
diff --git a/files/helpers/actions.py b/files/helpers/actions.py
index 3ef8e4390..94e0b98e7 100644
--- a/files/helpers/actions.py
+++ b/files/helpers/actions.py
@@ -166,7 +166,7 @@ def execute_snappy(post, v):
g.db.add(award_object)
awarded_coins = int(AWARDS["glowie"]['price'] * COSMETIC_AWARD_COIN_AWARD_PCT)
- post.author.pay_account('coins', awarded_coins)
+ post.author.pay_account('coins', awarded_coins, f"glowie award on {post.textlink}")
msg = f"@Snappy has given {post.textlink} the Glowie Award and you have received {awarded_coins} coins as a result!"
send_repeatable_notification(post.author.id, msg)
@@ -475,6 +475,7 @@ def execute_antispam_post_check(title, v, url):
return True
def execute_antispam_duplicate_comment_check(v, body_html):
+ return
if v.admin_level >= PERMS['BYPASS_ANTISPAM_CHECKS']:
return
if v.id in ANTISPAM_BYPASS_IDS:
@@ -496,6 +497,7 @@ def execute_antispam_duplicate_comment_check(v, body_html):
abort(403, "Too much spam!")
def execute_antispam_comment_check(body, v):
+ return
if v.admin_level >= PERMS['BYPASS_ANTISPAM_CHECKS']:
return
diff --git a/files/helpers/alerts.py b/files/helpers/alerts.py
index c77b925eb..c32ec1440 100644
--- a/files/helpers/alerts.py
+++ b/files/helpers/alerts.py
@@ -177,6 +177,7 @@ def NOTIFY_USERS(text, v, oldtext=None, ghost=False, obj=None, followers_ping=Tr
if FEATURES['PING_GROUPS']:
cost = 0
+ cost_groups = []
coin_receivers = set()
for i in group_mention_regex.finditer(text):
@@ -194,8 +195,11 @@ def NOTIFY_USERS(text, v, oldtext=None, ghost=False, obj=None, followers_ping=Tr
cost = g.db.query(User).count() * 5
if cost > v.coins + v.marseybux:
abort(403, f"You need {cost} currency to mention these ping groups!")
-
- v.charge_account('coins/marseybux', cost)
+
+ reason = f"group pinging cost (!everyone
)"
+ if obj:
+ reason += f" on {obj.textlink}"
+ v.charge_account('coins/marseybux', cost, reason)
if obj:
obj.ping_cost += cost
return 'everyone'
@@ -231,6 +235,7 @@ def NOTIFY_USERS(text, v, oldtext=None, ghost=False, obj=None, followers_ping=Tr
if group and group.name == 'verifiedrich':
abort(403, f"Only !verifiedrich members can mention it!")
cost += len(members) * 5
+ cost_groups.append(group.name)
if cost > v.coins + v.marseybux:
abort(403, f"You need {cost} currency to mention these ping groups!")
@@ -239,7 +244,10 @@ def NOTIFY_USERS(text, v, oldtext=None, ghost=False, obj=None, followers_ping=Tr
if charge:
if cost:
- v.charge_account('coins/marseybux', cost)
+ reason = f"group pinging cost (!" + "
, !".join(cost_groups) + "
)"
+ if obj:
+ reason += f" on {obj.textlink}"
+ v.charge_account('coins/marseybux', cost, reason)
if obj:
obj.ping_cost += cost
diff --git a/files/helpers/cron.py b/files/helpers/cron.py
index 3c15ed668..5d03c61d1 100644
--- a/files/helpers/cron.py
+++ b/files/helpers/cron.py
@@ -353,7 +353,7 @@ def _unpin_expired():
def _give_marseybux_salary():
for u in g.db.query(User).filter(User.admin_level > 0).all():
marseybux_salary = u.admin_level * 10000
- u.pay_account('marseybux', marseybux_salary)
+ u.pay_account('marseybux', marseybux_salary, "janny salary")
send_repeatable_notification(u.id, f"You have received your monthly janny salary of {marseybux_salary} Marseybux!")
def _expire_blocks_mutes_exiles():
diff --git a/files/helpers/lottery.py b/files/helpers/lottery.py
index 162eac028..f8eb3fab3 100644
--- a/files/helpers/lottery.py
+++ b/files/helpers/lottery.py
@@ -47,7 +47,7 @@ def end_lottery_session():
winner = random.choice(raffle)
active_lottery.winner_id = winner
winning_user = next(filter(lambda x: x.id == winner, participating_users))
- winning_user.pay_account('coins', active_lottery.prize)
+ winning_user.pay_account('coins', active_lottery.prize, "lottery winnings")
winning_user.total_lottery_winnings += active_lottery.prize
badge_grant(user=winning_user, badge_id=LOTTERY_WINNER_BADGE_ID)
@@ -109,7 +109,7 @@ def purchase_lottery_tickets(v, quantity=1):
if (most_recent_lottery is None):
return False, "There is no active lottery!"
- if not v.charge_account('coins', LOTTERY_TICKET_COST * quantity)[0]:
+ if not v.charge_account('coins', LOTTERY_TICKET_COST * quantity, f'cost of {quantity} lottery tickets')[0]:
return False, "You don't have enough coins"
v.currently_held_lottery_tickets += quantity
diff --git a/files/helpers/roulette.py b/files/helpers/roulette.py
index e491bf75f..496f7d7fc 100644
--- a/files/helpers/roulette.py
+++ b/files/helpers/roulette.py
@@ -85,7 +85,7 @@ def get_active_roulette_games():
def charge_gambler(gambler, amount, currency):
- charged = gambler.charge_account(currency, amount)[0]
+ charged = gambler.charge_account(currency, amount, "cost of roulette bet")[0]
if not charged:
raise Exception("Gambler cannot afford charge.")
@@ -179,8 +179,8 @@ def spin_roulette_wheel():
coin_winnings = gambler_payout['coins']
procoin_winnings = gambler_payout['marseybux']
- gambler.pay_account('coins', coin_winnings)
- gambler.pay_account('marseybux', procoin_winnings)
+ gambler.pay_account('coins', coin_winnings, "roulette winnings")
+ gambler.pay_account('marseybux', procoin_winnings, "roulette winnings")
# Notify the winners.
notification_text = f"Winning number: {number}\n\nCongratulations! One or more of your roulette bets paid off!\n\n"
diff --git a/files/helpers/slots.py b/files/helpers/slots.py
index 996b8aa13..d34194fc5 100644
--- a/files/helpers/slots.py
+++ b/files/helpers/slots.py
@@ -51,6 +51,19 @@ def casino_slot_pull(gambler, wager_value, currency):
g.db.add(casino_game)
g.db.flush()
+ if casino_game.winnings:
+ currency_log = CurrencyLog(
+ user_id=gambler.id,
+ currency=currency,
+ amount=-casino_game.winnings,
+ reason="slots bet",
+ )
+ g.db.add(currency_log)
+ if currency == 'coins':
+ currency_log.balance = gambler.coins
+ else:
+ currency_log.balance = gambler.marseybux
+
return casino_game.id, casino_game.game_state
else:
return None, "{}",
diff --git a/files/helpers/treasure.py b/files/helpers/treasure.py
index b58c0f391..889f1cc8d 100644
--- a/files/helpers/treasure.py
+++ b/files/helpers/treasure.py
@@ -40,5 +40,5 @@ def check_for_treasure(from_comment, in_text):
from_comment.treasure_amount = f'l{ticket_count}'
return
- user.pay_account('coins', amount)
+ user.pay_account('coins', amount, f"found treasure in {from_comment.textlink}")
from_comment.treasure_amount = str(amount)
diff --git a/files/helpers/twentyone.py b/files/helpers/twentyone.py
index 390157f1a..f27fde8b2 100644
--- a/files/helpers/twentyone.py
+++ b/files/helpers/twentyone.py
@@ -317,9 +317,21 @@ def handle_payout(gambler, state, game):
elif split_status == BlackjackStatus.PUSHED:
payout += game.wager
-
gambler.pay_account(game.currency, payout)
+ if game.winnings:
+ currency_log = CurrencyLog(
+ user_id=gambler.id,
+ currency=game.currency,
+ amount=-game.winnings,
+ reason="blackjack bet",
+ )
+ g.db.add(currency_log)
+ if currency == 'coins':
+ currency_log.balance = gambler.coins
+ else:
+ currency_log.balance = gambler.marseybux
+
if status in {BlackjackStatus.BLACKJACK, BlackjackStatus.WON} or split_status in {BlackjackStatus.WON}:
distribute_wager_badges(gambler, game.wager, won=True)
elif status == BlackjackStatus.LOST or split_status == BlackjackStatus.LOST:
diff --git a/files/routes/admin.py b/files/routes/admin.py
index 8b25db09f..08b1f952e 100644
--- a/files/routes/admin.py
+++ b/files/routes/admin.py
@@ -188,7 +188,7 @@ def distribute(v, kind, option_id):
cid = notif_comment(text)
for vote in votes:
u = vote.user
- u.pay_account('coins', coinsperperson)
+ u.pay_account('coins', coinsperperson, f"bet winnings on {parent.textlink}")
add_notif(cid, u.id, text, pushnotif_url=parent.permalink)
text = f"You lost the {POLL_BET_COINS} coins you bet on {parent.textlink} :marseylaugh:"
@@ -2163,7 +2163,7 @@ def mark_effortpost(pid, v):
coins = (p.upvotes + p.downvotes) * mul
- p.author.pay_account('coins', coins)
+ p.author.pay_account('coins', coins, f"retroactive efortpost gains of {post.textlink}")
if v.id != p.author_id:
send_repeatable_notification(p.author_id, f":marseyclapping: @{v.username} (a site admin) has marked {p.textlink} as an effortpost, it now gets x{mul} coins from votes. You have received {coins} coins retroactively, thanks! :!marseyclapping:")
@@ -2200,7 +2200,7 @@ def unmark_effortpost(pid, v):
coins = (p.upvotes + p.downvotes) * mul
- p.author.charge_account('coins', coins)
+ p.author.charge_account('coins', coins, f"revocation of efortpost gains of {post.textlink}")
if v.id != p.author_id:
send_repeatable_notification(p.author_id, f":marseyitsover: @{v.username} (a site admin) has unmarked {p.textlink} as an effortpost. {coins} coins have been deducted from you. :!marseyitsover:")
diff --git a/files/routes/asset_submissions.py b/files/routes/asset_submissions.py
index 4711b31e4..ef25530ab 100644
--- a/files/routes/asset_submissions.py
+++ b/files/routes/asset_submissions.py
@@ -225,7 +225,7 @@ def approve_emoji(v, name):
if 'pkmn' in emoji.tags: amount = 500
else: amount = 250
- author.pay_account('coins', amount)
+ author.pay_account('coins', amount, f"reward for making :{emoji.name}:")
g.db.add(author)
if v.id != author.id:
diff --git a/files/routes/awards.py b/files/routes/awards.py
index 7035f435a..e053c7662 100644
--- a/files/routes/awards.py
+++ b/files/routes/awards.py
@@ -64,7 +64,7 @@ def buy_award(v, kind, AWARDS):
else:
currency = 'coins/marseybux'
- charged = v.charge_account(currency, price)
+ charged = v.charge_account(currency, price, f"{kind} award cost")
if not charged[0]:
abort(400, f"Not enough {currency}!")
@@ -238,8 +238,8 @@ def award_thing(v, thing_type, id):
if kind == 'shit':
awarded_coins = int(AWARDS[kind]['price'] * COSMETIC_AWARD_COIN_AWARD_PCT)
- v.charge_account('coins', awarded_coins, should_check_balance=False)
- obj.author.pay_account('coins', awarded_coins)
+ v.charge_account('coins', awarded_coins, f"shit award deflected theft on {obj.textlink}", should_check_balance=False)
+ obj.author.pay_account('coins', awarded_coins, f"shit award deflected theft on {obj.textlink}")
elif kind != 'spider':
if AWARDS[kind]['cosmetic'] and not AWARDS[kind]['included_in_lootbox']:
awarded_coins = int(AWARDS[kind]['price'] * COSMETIC_AWARD_COIN_AWARD_PCT)
@@ -248,8 +248,8 @@ def award_thing(v, thing_type, id):
if awarded_coins:
if kind == 'shit':
- author.charge_account('coins', awarded_coins, should_check_balance=False)
- v.pay_account('coins', awarded_coins)
+ author.charge_account('coins', awarded_coins, f"shit award theft on {obj.textlink}", should_check_balance=False)
+ v.pay_account('coins', awarded_coins, f"shit award theft on {obj.textlink}")
else:
author.pay_account('coins', awarded_coins)
@@ -479,7 +479,7 @@ def award_thing(v, thing_type, id):
author.patron = 1
if author.patron_utc: author.patron_utc += 2629746
else: author.patron_utc = int(time.time()) + 2629746
- author.pay_account('marseybux', 1250)
+ author.pay_account('marseybux', 1250, f"benefactor award on {obj.textlink}")
badge_grant(user=v, badge_id=103)
elif kind == "rehab":
if author.rehab: author.rehab += 86400
diff --git a/files/routes/groups.py b/files/routes/groups.py
index 71b273692..baad74709 100644
--- a/files/routes/groups.py
+++ b/files/routes/groups.py
@@ -35,7 +35,7 @@ def create_group(v):
if name in {'everyone', 'jannies', 'holejannies', 'followers', 'commenters'} or g.db.get(Group, name):
abort(400, "This group already exists!")
- if not v.charge_account('coins/marseybux', GROUP_COST)[0]:
+ if not v.charge_account('coins/marseybux', GROUP_COST, f"cost of creating !{name}")[0]:
abort(403, "You don't have enough coins or marseybux!")
g.db.add(v)
diff --git a/files/routes/hats.py b/files/routes/hats.py
index dc34b1471..3c0f65804 100644
--- a/files/routes/hats.py
+++ b/files/routes/hats.py
@@ -44,12 +44,12 @@ def buy_hat(v, hat_id):
if not hat.is_purchasable:
abort(403, "This hat is not for sale!")
- charged = v.charge_account('coins/marseybux', hat.price)
+ charged = v.charge_account('coins/marseybux', hat.price, f"{hat.name} hat cost")
if not charged[0]:
abort(400, "Not enough coins/marseybux!")
v.coins_spent_on_hats += charged[1]
- hat.author.pay_account('coins', hat.price * 0.1)
+ hat.author.pay_account('coins', hat.price * 0.1, f"royalties for `{hat.name}`")
new_hat = Hat(user_id=v.id, hat_id=hat.id)
g.db.add(new_hat)
diff --git a/files/routes/holes.py b/files/routes/holes.py
index 897692e4b..47ee405d4 100644
--- a/files/routes/holes.py
+++ b/files/routes/holes.py
@@ -393,7 +393,7 @@ def create_sub2(v):
if not hole_group_name_regex.fullmatch(name):
abort(400, "Name does not match the required format!")
- if not v.charge_account('coins/marseybux', HOLE_COST)[0]:
+ if not v.charge_account('coins/marseybux', HOLE_COST, f"cost of creating /h/{name}")[0]:
abort(400, "You don't have enough coins or marseybux!")
hole = get_hole(name, graceful=True)
diff --git a/files/routes/polls.py b/files/routes/polls.py
index 22a16b44f..41b16ae7c 100644
--- a/files/routes/polls.py
+++ b/files/routes/polls.py
@@ -26,7 +26,7 @@ def vote_option(option_id, v):
if option.exclusive == 2:
if option.parent.total_bet_voted(v):
abort(403, "You can't participate in a closed bet!")
- if not v.charge_account('coins/marseybux', POLL_BET_COINS)[0]:
+ if not v.charge_account('coins/marseybux', POLL_BET_COINS, f"cost of bet on {option.parent.textlink}")[0]:
abort(400, f"You don't have {POLL_BET_COINS} coins or marseybux!")
g.db.add(v)
@@ -79,7 +79,7 @@ def vote_option_comment(option_id, v):
if option.exclusive == 2:
if option.parent.total_bet_voted(v):
abort(403, "You can't participate in a closed bet!")
- if not v.charge_account('coins/marseybux', POLL_BET_COINS)[0]:
+ if not v.charge_account('coins/marseybux', POLL_BET_COINS, f"cost of bet on {option.parent.textlink}")[0]:
abort(400, f"You don't have {POLL_BET_COINS} coins or marseybux!")
g.db.add(v)
diff --git a/files/routes/settings.py b/files/routes/settings.py
index 246657eca..28e0a7a8f 100644
--- a/files/routes/settings.py
+++ b/files/routes/settings.py
@@ -381,7 +381,7 @@ def settings_personal_post(v):
else:
cost = HOUSE_JOIN_COST
- success = v.charge_account('coins/marseybux', cost)[0]
+ success = v.charge_account('coins/marseybux', cost, "cost of changing houses")[0]
if not success: abort(403)
if house == "None":
diff --git a/files/routes/users.py b/files/routes/users.py
index 128d1cda2..20fc1fbdb 100644
--- a/files/routes/users.py
+++ b/files/routes/users.py
@@ -78,7 +78,7 @@ def claim_rewards_all_users():
marseybux = int(marseybux)
text = f"You have received {marseybux} Marseybux! You can use them to buy awards or hats in the [shop](/shop/awards) or gamble them in the [casino](/casino)."
- user.pay_account('marseybux', marseybux)
+ user.pay_account('marseybux', marseybux, f"{patron.lower()} reward")
send_repeatable_notification(user.id, text)
g.db.add(user)
@@ -147,13 +147,13 @@ def transfer_currency(v, username, currency_name, apply_tax):
notif_text += f"\n\n> {reason}"
log_message += f"\n\n> {reason}"
- if not v.charge_account(currency_name, amount)[0]:
+ if not v.charge_account(currency_name, amount, f"gift to @{username}")[0]:
abort(400, f"You don't have enough {currency_name}")
if currency_name == 'marseybux':
- receiver.pay_account('marseybux', amount - tax)
+ receiver.pay_account('marseybux', amount - tax, f"gift from @{v.username}")
elif currency_name == 'coins':
- receiver.pay_account('coins', amount - tax)
+ receiver.pay_account('coins', amount - tax, f"gift from @{v.username}")
else:
raise ValueError(f"Invalid currency '{currency_name}' got when transferring {amount} from {v.id} to {receiver.id}")
@@ -1549,3 +1549,18 @@ def usersong(username):
@auth_required
def user_effortposts(v, username):
return redirect(f'/search/posts?q=author:{username}+effortpost:true')
+
+@app.get("/bank_statement")
+@limiter.limit(DEFAULT_RATELIMIT, deduct_when=lambda response: response.status_code < 400)
+@limiter.limit(DEFAULT_RATELIMIT, deduct_when=lambda response: response.status_code < 400, key_func=get_ID)
+@auth_required
+def currency_log(v):
+ page = get_page()
+
+ logs = g.db.query(CurrencyLog).filter_by(user_id=v.id)
+
+ total = logs.count()
+
+ logs = logs.order_by(CurrencyLog.created_utc.desc()).offset(PAGE_SIZE * (page - 1)).limit(PAGE_SIZE).all()
+
+ return render_template("bank_statement.html", v=v, logs=logs, page=page, total=total)
diff --git a/files/templates/bank_statement.html b/files/templates/bank_statement.html
new file mode 100644
index 000000000..c46bd2b97
--- /dev/null
+++ b/files/templates/bank_statement.html
@@ -0,0 +1,38 @@
+{% extends "default.html" %}
+{% block pagetitle %}Bank Statement{% endblock %}
+{% block content %}
+