add bank statement

pull/225/head
Aevann 2024-03-02 19:02:05 +02:00
parent 6be9c7ac34
commit 2517e385a6
30 changed files with 226 additions and 43 deletions

View File

@ -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;
}

View File

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

View File

@ -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 *

View File

@ -324,7 +324,7 @@ class Comment(Base):
@property
@lazy
def textlink(self):
return f"[this comment]({self.shortlink})"
return f'<a href="{self.shortlink}">this comment</a>'
@property
@lazy

View File

@ -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)

View File

@ -156,7 +156,7 @@ class Post(Base):
@property
@lazy
def textlink(self):
return f"[{self.title}]({self.shortlink})"
return f'<a href="{self.shortlink}">{self.title}</a>'
@property
@lazy

View File

@ -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)

View File

@ -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

View File

@ -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 (<code>!everyone</code>)"
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 (<code>!" + "</code>, <code>!".join(cost_groups) + "</code>)"
if obj:
reason += f" on {obj.textlink}"
v.charge_account('coins/marseybux', cost, reason)
if obj:
obj.ping_cost += cost

View File

@ -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():

View File

@ -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

View File

@ -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"

View File

@ -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, "{}",

View File

@ -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)

View File

@ -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:

View File

@ -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:")

View File

@ -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:

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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":

View File

@ -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)

View File

@ -0,0 +1,38 @@
{% extends "default.html" %}
{% block pagetitle %}Bank Statement{% endblock %}
{% block content %}
<div class="row justify-content-around">
<div class="col h-100">
<div class="justify-content-between">
<h2 class="font-weight-bolder text-center pt-2 pb-3">Bank Statement</h2>
</div>
<h5 class="font-weight-bolder text-right pb-0 mr-4">Balance</h2>
<div class="rounded border mx-auto">
{% for log in logs %}
<div class="modlog-action {% if log.amount > 0 %}bg-green{% else %}bg-red{% endif %} ">
<div class="d-flex flex-grow-1 align-items-center">
<div class="d-flex align-items-center justify-content-center mr-3 rounded-lg flex-shrink-0" style="width: 32px;height: 32px">
<img width="50" alt="{{log.currency}}" class="pl-2" data-bs-toggle="tooltip" data-bs-placement="bottom" src="{{SITE_FULL_IMAGES}}/i/{{log.currency}}.webp?x=7" title="{{log.currency.title()}}">
</div>
<div class="d-flex align-items-center">
<div class="text-muted pl-3">
<div>
<span><b>{{log.amount}} {{log.currency}}</b> &nbsp; {{log.reason | safe}}</span>
</div>
<div class="text-gray-500">
<span class="log--item-age" data-bs-toggle="tooltip" data-bs-placement="bottom" data-nonce="{{g.nonce}}" data-onmouseover="timestamp(this, '{{log.created_utc}}')">{{log.age_string}}</span>
</div>
</div>
</div>
<span class="ml-auto">{{log.balance}} {{log.currency}}</span>
</div>
</div>
{% else %}
<div class="p-3">There's nothing here right now.</div>
{% endfor %}
</div>
{% include "pagination.html" %}
</div>
</div>
{% endblock %}

View File

@ -337,7 +337,7 @@
</div>
<div class="text-left pl-2" id="header--username--outerdiv">
<div style="color: #{{v.name_color}}" class="text-small font-weight-bold"><span id="header--username" {% if v.patron %}class="patron" style="background-color:#{{v.name_color}}"{% endif %} {% if v.pride_username(v) %}pride_username{% endif %}>{{v.username}}</span></div>
<div class="header--currency"><img loading="lazy" alt="coins" class="mr-1 ml-1" data-bs-toggle="tooltip" data-bs-placement="bottom" src="{{'coins.webp' | asset_siteimg}}" title="Coins"><span id="user-coins-amount">{{v.coins}}</span>{% if not FEATURES['MARSEYBUX'] %} Coin{{macros.plural(v.coins)}}{% endif %}</div>
<div class="header--currency"><img loading="lazy" alt="coins" class="mr-1 ml-1" data-bs-toggle="tooltip" data-bs-placement="bottom" src="{{SITE_FULL_IMAGES}}/i/coins.webp?x=7" title="Coins"><span id="user-coins-amount">{{v.coins}}</span>{% if not FEATURES['MARSEYBUX'] %} Coin{{macros.plural(v.coins)}}{% endif %}</div>
{% if FEATURES['MARSEYBUX'] %}
<div class="header--currency"><img loading="lazy" alt="marseybux" class="mr-1 ml-1" data-bs-toggle="tooltip" data-bs-placement="bottom" src="{{SITE_FULL_IMAGES}}/i/marseybux.webp?x=7" title="Marseybux"><span id="user-bux-amount">{{v.marseybux}}</span></div>
{% endif %}
@ -415,7 +415,7 @@
{% if v %}
<li class="nav-item">
<div class="header--currency ml-2 pl-1 my-2">
<img loading="lazy" alt="coins" data-bs-toggle="tooltip" data-bs-placement="bottom" src="{{'coins.webp' | asset_siteimg}}" title="Coins">
<img loading="lazy" alt="coins" data-bs-toggle="tooltip" data-bs-placement="bottom" src="{{SITE_FULL_IMAGES}}/i/coins.webp?x=7" title="Coins">
<span id="user-coins-amount-mobile">{{v.coins}}</span>
</div>
{% if FEATURES['MARSEYBUX'] %}

View File

@ -28,7 +28,7 @@
data-bs-toggle="tooltip"
data-bs-placement="bottom"
height="13"
src="{{'coins.webp' | asset_siteimg}}"
src="{{SITE_FULL_IMAGES}}/i/coins.webp?x=7"
title="Coins"
style="display: none; position: relative; top: -2px"
>
@ -93,7 +93,7 @@
data-bs-toggle="tooltip"
data-bs-placement="bottom"
height="13"
src="{{'coins.webp' | asset_siteimg}}"
src="{{SITE_FULL_IMAGES}}/i/coins.webp?x=7"
title="Coins"
>

View File

@ -106,7 +106,7 @@
</div>
<span id="profile-coins-amount">{{u.coins}}</span>
<img loading="lazy" alt="coins" class="ml-1 mb-1 mr-2" data-bs-toggle="tooltip" data-bs-placement="bottom" title="Coins" height="20" src="{{'coins.webp' | asset_siteimg}}">
<img loading="lazy" alt="coins" class="ml-1 mb-1 mr-2" data-bs-toggle="tooltip" data-bs-placement="bottom" title="Coins" height="20" src="{{SITE_FULL_IMAGES}}/i/coins.webp?x=7">
{% if FEATURES['MARSEYBUX'] %}
<span id="profile-bux-amount">{{u.marseybux}}</span>
@ -415,7 +415,7 @@
</div>
<span id="profile-coins-amount-mobile" class="font-weight-bold">{{u.coins}}</span>
<img loading="lazy" alt="coins" class="ml-1 mb-1 mr-2" data-bs-toggle="tooltip" data-bs-placement="bottom" title="Coins" height="15" src="{{'coins.webp' | asset_siteimg}}">
<img loading="lazy" alt="coins" class="ml-1 mb-1 mr-2" data-bs-toggle="tooltip" data-bs-placement="bottom" title="Coins" height="15" src="{{SITE_FULL_IMAGES}}/i/coins.webp?x=7">
{% if FEATURES['MARSEYBUX'] %}
<span id="profile-bux-amount-mobile" class="font-weight-bold">{{u.marseybux}}</span>

View File

@ -0,0 +1,26 @@
create table currency_logs (
id integer primary key,
user_id integer not null,
created_utc integer not null,
currency varchar(9) not null,
amount integer not null,
reason varchar(1000) not null,
balance integer not null
);
CREATE SEQUENCE public.currency_logs_id_seq
AS integer
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;
ALTER SEQUENCE public.currency_logs_id_seq OWNED BY public.currency_logs.id;
ALTER TABLE ONLY public.currency_logs ALTER COLUMN id SET DEFAULT nextval('public.currency_logs_id_seq'::regclass);
alter table only currency_logs
add constraint currency_logs_user_fkey foreign key (user_id) references public.users(id);
create index currency_logs_index on currency_logs using btree (user_id);