feat: add backup CLI commands, AI correction/modifier services, and timezone-aware date display
DevPlace CI / test (push) Failing after 25m18s
DevPlace CI / test (push) Failing after 25m18s
- Add `devplace backups` CLI subcommands (list, run, prune, clear) with job enqueueing and orphan cleanup - Introduce `BACKUPS_DIR` and `BACKUP_STAGING_DIR` config paths for backup storage - Implement `schedule_correction` and `schedule_modification` calls in content creation, comment creation, and comment editing flows - Add `DEFAULT_CORRECTION_PROMPT` and `DEFAULT_MODIFIER_PROMPT` config constants for AI content processing - Document timezone-aware date display using `local_dt`/`dt_ago` Jinja globals with client-side `Intl` localization - Update README with AI content correction/modifier support in direct messages via `@ai` inline instructions - Add `track_action(user["uid"], "vote")` call on upvote in `apply_vote`
This commit is contained in:
@@ -34,6 +34,8 @@ from devplacepy.utils import (
|
||||
clear_user_cache,
|
||||
not_found,
|
||||
generate_uid,
|
||||
track_action,
|
||||
build_achievements,
|
||||
)
|
||||
from devplacepy.responses import respond, action_result
|
||||
from devplacepy.schemas import ProfileOut
|
||||
@@ -45,8 +47,11 @@ from devplacepy.seo import (
|
||||
profile_page_schema,
|
||||
)
|
||||
from devplacepy.services.audit import record as audit
|
||||
from devplacepy.services.correction import schedule_correction
|
||||
from devplacepy.services.ai_modifier import schedule_modification
|
||||
from devplacepy.config import DEFAULT_CORRECTION_PROMPT, DEFAULT_MODIFIER_PROMPT
|
||||
|
||||
from devplacepy.routers.profile.usage import _ai_quota
|
||||
from devplacepy.routers.profile.usage import _ai_quota, _correction_usage, _modifier_usage
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
router = APIRouter()
|
||||
@@ -139,7 +144,10 @@ async def profile_page(
|
||||
posts_table = get_table("posts")
|
||||
raw_posts = list(
|
||||
posts_table.find(
|
||||
user_uid=profile_user["uid"], deleted_at=None, order_by=["-created_at"]
|
||||
user_uid=profile_user["uid"],
|
||||
deleted_at=None,
|
||||
order_by=["-created_at"],
|
||||
_limit=10,
|
||||
)
|
||||
)
|
||||
post_uids = [p["uid"] for p in raw_posts]
|
||||
@@ -162,6 +170,9 @@ async def profile_page(
|
||||
item["poll"] = polls_map.get(uid)
|
||||
|
||||
badges = list(get_table("badges").find(user_uid=profile_user["uid"]))
|
||||
achievements = build_achievements({b["badge_name"] for b in badges})
|
||||
badge_total = sum(group["total"] for group in achievements)
|
||||
badge_earned = sum(group["earned"] for group in achievements)
|
||||
projects = list(
|
||||
get_table("projects").find(user_uid=profile_user["uid"], deleted_at=None)
|
||||
)
|
||||
@@ -172,7 +183,7 @@ async def profile_page(
|
||||
gists = []
|
||||
for g in gists_raw:
|
||||
gists.append({"gist": g, "time_ago": time_ago(g["created_at"])})
|
||||
posts_count = len(posts) or get_table("posts").count(
|
||||
posts_count = get_table("posts").count(
|
||||
user_uid=profile_user["uid"], deleted_at=None
|
||||
)
|
||||
|
||||
@@ -234,6 +245,28 @@ async def profile_page(
|
||||
viewer_is_admin = bool(current_user and current_user.get("role") == "Admin")
|
||||
can_view_api_key = bool(current_user and (is_owner or viewer_is_admin))
|
||||
api_key = profile_user.get("api_key", "") if can_view_api_key else ""
|
||||
ai_correction_enabled = (
|
||||
bool(profile_user.get("ai_correction_enabled")) if is_owner else False
|
||||
)
|
||||
ai_correction_sync = (
|
||||
bool(profile_user.get("ai_correction_sync")) if is_owner else False
|
||||
)
|
||||
ai_correction_prompt = (
|
||||
(profile_user.get("ai_correction_prompt") or DEFAULT_CORRECTION_PROMPT)
|
||||
if is_owner
|
||||
else None
|
||||
)
|
||||
ai_modifier_enabled = (
|
||||
bool(profile_user.get("ai_modifier_enabled")) if is_owner else False
|
||||
)
|
||||
ai_modifier_sync = (
|
||||
bool(profile_user.get("ai_modifier_sync")) if is_owner else False
|
||||
)
|
||||
ai_modifier_prompt = (
|
||||
(profile_user.get("ai_modifier_prompt") or DEFAULT_MODIFIER_PROMPT)
|
||||
if is_owner
|
||||
else None
|
||||
)
|
||||
profile_is_admin = profile_user.get("role") == "Admin"
|
||||
ai_quota = (
|
||||
_ai_quota(
|
||||
@@ -242,6 +275,16 @@ async def profile_page(
|
||||
if (is_owner or viewer_is_admin)
|
||||
else None
|
||||
)
|
||||
correction_usage = (
|
||||
_correction_usage(profile_user["uid"], include_cost=viewer_is_admin)
|
||||
if (is_owner or viewer_is_admin)
|
||||
else None
|
||||
)
|
||||
modifier_usage = (
|
||||
_modifier_usage(profile_user["uid"], include_cost=viewer_is_admin)
|
||||
if (is_owner or viewer_is_admin)
|
||||
else None
|
||||
)
|
||||
can_manage_customization = is_owner or viewer_is_admin
|
||||
customization_prefs = (
|
||||
get_customization_prefs("user", profile_user["uid"])
|
||||
@@ -291,6 +334,9 @@ async def profile_page(
|
||||
"profile_user": profile_user,
|
||||
"posts": posts,
|
||||
"badges": badges,
|
||||
"achievements": achievements,
|
||||
"badge_total": badge_total,
|
||||
"badge_earned": badge_earned,
|
||||
"projects": projects,
|
||||
"gists": gists,
|
||||
"current_tab": tab,
|
||||
@@ -302,11 +348,19 @@ async def profile_page(
|
||||
"media_pagination": media_pagination,
|
||||
"can_view_api_key": can_view_api_key,
|
||||
"api_key": api_key,
|
||||
"ai_correction_enabled": ai_correction_enabled,
|
||||
"ai_correction_sync": ai_correction_sync,
|
||||
"ai_correction_prompt": ai_correction_prompt,
|
||||
"ai_modifier_enabled": ai_modifier_enabled,
|
||||
"ai_modifier_sync": ai_modifier_sync,
|
||||
"ai_modifier_prompt": ai_modifier_prompt,
|
||||
"can_manage_customization": can_manage_customization,
|
||||
"cust_disable_global": customization_prefs["disable_global"],
|
||||
"cust_disable_pagetype": customization_prefs["disable_pagetype"],
|
||||
"notification_prefs": notification_prefs,
|
||||
"ai_quota": ai_quota,
|
||||
"correction_usage": correction_usage,
|
||||
"modifier_usage": modifier_usage,
|
||||
"activities": activities,
|
||||
"rank": rank,
|
||||
"heatmap": heatmap,
|
||||
@@ -336,6 +390,8 @@ async def update_profile(request: Request, data: Annotated[ProfileForm, Form()])
|
||||
["uid"],
|
||||
)
|
||||
clear_user_cache(user["uid"])
|
||||
schedule_correction(user, "users", user["uid"], request)
|
||||
schedule_modification(user, "users", user["uid"], request)
|
||||
|
||||
logger.info(f"Profile updated for {user['username']}")
|
||||
audit.record(
|
||||
@@ -349,6 +405,11 @@ async def update_profile(request: Request, data: Annotated[ProfileForm, Form()])
|
||||
summary=f"{user['username']} updated their profile",
|
||||
links=[audit.target("user", user["uid"], user["username"])],
|
||||
)
|
||||
if any(
|
||||
value.strip()
|
||||
for value in (data.bio, data.location, data.git_link, data.website)
|
||||
):
|
||||
track_action(user["uid"], "profile")
|
||||
return action_result(request, f"/profile/{user['username']}")
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user