forked from retoor/devplacepy
Update
This commit is contained in:
@@ -0,0 +1,193 @@
|
||||
# retoor <retoor@molodetz.nl>
|
||||
|
||||
from datetime import datetime, timedelta, timezone
|
||||
|
||||
from devplacepy.config import (
|
||||
AWARD_DISPLAY_HOURS_DEFAULT,
|
||||
AWARD_GIVE_COOLDOWN_HOURS_DEFAULT,
|
||||
AWARD_RECEIVE_COOLDOWN_HOURS_DEFAULT,
|
||||
)
|
||||
from .core import db
|
||||
from .pagination import build_pagination
|
||||
from .settings import get_int_setting
|
||||
from .core import get_table, _now_iso
|
||||
from .users import get_users_by_uids
|
||||
from .content import resolve_by_slug
|
||||
from .soft_delete import soft_delete, soft_delete_in
|
||||
|
||||
AWARDS_PER_PAGE = 12
|
||||
|
||||
|
||||
def _awards_table():
|
||||
return get_table("awards")
|
||||
|
||||
|
||||
def award_give_cooldown_hours() -> int:
|
||||
return max(1, get_int_setting("award_give_cooldown_hours", AWARD_GIVE_COOLDOWN_HOURS_DEFAULT))
|
||||
|
||||
|
||||
def award_receive_cooldown_hours() -> int:
|
||||
return max(
|
||||
1, get_int_setting("award_receive_cooldown_hours", AWARD_RECEIVE_COOLDOWN_HOURS_DEFAULT)
|
||||
)
|
||||
|
||||
|
||||
def award_display_hours() -> int:
|
||||
return max(1, get_int_setting("award_display_hours", AWARD_DISPLAY_HOURS_DEFAULT))
|
||||
|
||||
|
||||
def _cooldown_cutoff(hours: int) -> str:
|
||||
return (datetime.now(timezone.utc) - timedelta(hours=hours)).isoformat()
|
||||
|
||||
|
||||
def has_giver_cooldown(giver_uid: str) -> bool:
|
||||
if not giver_uid or "awards" not in db.tables:
|
||||
return False
|
||||
cutoff = _cooldown_cutoff(award_give_cooldown_hours())
|
||||
row = _awards_table().find_one(
|
||||
giver_uid=giver_uid, deleted_at=None, created_at={">=": cutoff}
|
||||
)
|
||||
return row is not None
|
||||
|
||||
|
||||
def has_receiver_cooldown(receiver_uid: str) -> bool:
|
||||
if not receiver_uid or "awards" not in db.tables:
|
||||
return False
|
||||
cutoff = _cooldown_cutoff(award_receive_cooldown_hours())
|
||||
row = _awards_table().find_one(
|
||||
receiver_uid=receiver_uid, deleted_at=None, created_at={">=": cutoff}
|
||||
)
|
||||
return row is not None
|
||||
|
||||
|
||||
def can_receive_award(receiver_uid: str) -> bool:
|
||||
return not has_receiver_cooldown(receiver_uid)
|
||||
|
||||
|
||||
def can_give_award(giver_uid: str, receiver_uid: str) -> bool:
|
||||
if not giver_uid or not receiver_uid or giver_uid == receiver_uid:
|
||||
return False
|
||||
return not has_giver_cooldown(giver_uid) and not has_receiver_cooldown(receiver_uid)
|
||||
|
||||
|
||||
def _published_filter():
|
||||
return {"deleted_at": None, "generated_at": {">": ""}}
|
||||
|
||||
|
||||
def count_published_awards(receiver_uid: str) -> int:
|
||||
if not receiver_uid or "awards" not in db.tables:
|
||||
return 0
|
||||
return _awards_table().count(receiver_uid=receiver_uid, **_published_filter())
|
||||
|
||||
|
||||
def _latest_published(receiver_uid: str):
|
||||
if not receiver_uid or "awards" not in db.tables:
|
||||
return None
|
||||
rows = list(
|
||||
_awards_table().find(
|
||||
receiver_uid=receiver_uid,
|
||||
deleted_at=None,
|
||||
generated_at={">": ""},
|
||||
order_by=["-generated_at"],
|
||||
_limit=1,
|
||||
)
|
||||
)
|
||||
return rows[0] if rows else None
|
||||
|
||||
|
||||
def recompute_user_award_stats(receiver_uid: str) -> None:
|
||||
if not receiver_uid or "users" not in db.tables:
|
||||
return
|
||||
count = count_published_awards(receiver_uid)
|
||||
latest = _latest_published(receiver_uid)
|
||||
users = get_table("users")
|
||||
payload = {
|
||||
"uid": receiver_uid,
|
||||
"award_count": count,
|
||||
"last_award_at": latest.get("generated_at") if latest else None,
|
||||
"last_award_slug": latest.get("slug") if latest else None,
|
||||
"last_award_uid": latest.get("uid") if latest else None,
|
||||
}
|
||||
users.update(payload, ["uid"])
|
||||
|
||||
|
||||
def award_is_prominent(user: dict | None) -> bool:
|
||||
if not user or not user.get("last_award_at") or not user.get("last_award_uid"):
|
||||
return False
|
||||
award = resolve_by_slug(_awards_table(), user["last_award_uid"])
|
||||
if not award or not award.get("generated_at"):
|
||||
return False
|
||||
try:
|
||||
published = datetime.fromisoformat(award["generated_at"])
|
||||
if published.tzinfo is None:
|
||||
published = published.replace(tzinfo=timezone.utc)
|
||||
except (ValueError, TypeError):
|
||||
return False
|
||||
window = timedelta(hours=award_display_hours())
|
||||
return datetime.now(timezone.utc) - published <= window
|
||||
|
||||
|
||||
def enrich_award(row: dict, givers: dict | None = None) -> dict:
|
||||
item = dict(row)
|
||||
giver_uid = row.get("giver_uid", "")
|
||||
giver = (givers or {}).get(giver_uid) or get_users_by_uids([giver_uid]).get(giver_uid)
|
||||
item["giver"] = giver
|
||||
item["image_url"] = f"/awards/{row.get('slug', '')}/256"
|
||||
item["thumb_url"] = f"/awards/{row.get('slug', '')}/64"
|
||||
return item
|
||||
|
||||
|
||||
def get_user_awards(receiver_uid: str, page: int = 1, per_page: int = AWARDS_PER_PAGE):
|
||||
if not receiver_uid or "awards" not in db.tables:
|
||||
return [], build_pagination(page, 0, per_page)
|
||||
table = _awards_table()
|
||||
total = table.count(receiver_uid=receiver_uid, **_published_filter())
|
||||
offset = max(0, (page - 1) * per_page)
|
||||
rows = list(
|
||||
table.find(
|
||||
receiver_uid=receiver_uid,
|
||||
deleted_at=None,
|
||||
generated_at={">": ""},
|
||||
order_by=["-generated_at"],
|
||||
_limit=per_page,
|
||||
_offset=offset,
|
||||
)
|
||||
)
|
||||
giver_uids = [row.get("giver_uid") for row in rows if row.get("giver_uid")]
|
||||
givers = get_users_by_uids(giver_uids)
|
||||
items = [enrich_award(row, givers) for row in rows]
|
||||
return items, build_pagination(page, total, per_page)
|
||||
|
||||
|
||||
def get_prominent_award(profile_user: dict) -> dict | None:
|
||||
if not award_is_prominent(profile_user):
|
||||
return None
|
||||
award = resolve_by_slug(_awards_table(), profile_user.get("last_award_uid", ""))
|
||||
if not award:
|
||||
return None
|
||||
return enrich_award(award)
|
||||
|
||||
|
||||
def revoke_award(award_uid: str, admin_uid: str) -> dict | None:
|
||||
table = _awards_table()
|
||||
row = table.find_one(uid=award_uid)
|
||||
if not row or row.get("deleted_at"):
|
||||
return None
|
||||
stamp = _now_iso()
|
||||
attachment_uids = [
|
||||
uid
|
||||
for uid in (
|
||||
row.get("attachment_uid_512"),
|
||||
row.get("attachment_uid_256"),
|
||||
row.get("attachment_uid_64"),
|
||||
)
|
||||
if uid
|
||||
]
|
||||
soft_delete("awards", admin_uid, stamp=stamp, uid=award_uid)
|
||||
from devplacepy.attachments import soft_delete_attachments_for
|
||||
|
||||
soft_delete_attachments_for("award", [award_uid], admin_uid)
|
||||
if attachment_uids:
|
||||
soft_delete_in("attachments", "uid", attachment_uids, admin_uid, stamp=stamp)
|
||||
recompute_user_award_stats(row.get("receiver_uid", ""))
|
||||
return row
|
||||
Reference in New Issue
Block a user