feat(m5a): channel manager, tabbed settings panel, per-user sync status
Backend: /api/channels (list + PATCH priority/hidden + attach/detach user tags), user-tag CRUD on /api/tags, /api/sync/my-status (per-user channel sync counts). Frontend: feed|channels page nav (URL-synced) from the account menu; a slide-in tabbed Settings panel (Appearance, Notifications=6b sound+duration, Sync status + actions + admin pause/resume, Account); a channel manager with priority, hide, per-channel user tags, sync badges and 'view in feed'. Notifications now honor the configurable sound + auto-dismiss settings.
This commit is contained in:
parent
b8a8605797
commit
a8822d3935
11 changed files with 1063 additions and 45 deletions
|
|
@ -1,5 +1,5 @@
|
|||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from sqlalchemy import func, select
|
||||
from sqlalchemy import and_, case, func, select
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app import quota, state
|
||||
|
|
@ -93,6 +93,50 @@ def sync_status(
|
|||
}
|
||||
|
||||
|
||||
@router.get("/my-status")
|
||||
def my_status(
|
||||
user: User = Depends(current_user), db: Session = Depends(get_db)
|
||||
) -> dict:
|
||||
"""Per-user sync progress: how far the channels *this* user subscribes to have synced."""
|
||||
sub_join = and_(
|
||||
Subscription.channel_id == Channel.id, Subscription.user_id == user.id
|
||||
)
|
||||
total, details_synced, recent_synced, deep_done = db.execute(
|
||||
select(
|
||||
func.count(Channel.id),
|
||||
func.count(Channel.details_synced_at),
|
||||
func.count(Channel.recent_synced_at),
|
||||
func.sum(case((Channel.backfill_done.is_(True), 1), else_=0)),
|
||||
)
|
||||
.select_from(Channel)
|
||||
.join(Subscription, sub_join)
|
||||
).one()
|
||||
total = total or 0
|
||||
deep_done = int(deep_done or 0)
|
||||
my_videos = db.scalar(
|
||||
select(func.count(Video.id)).join(
|
||||
Subscription,
|
||||
and_(
|
||||
Subscription.channel_id == Video.channel_id,
|
||||
Subscription.user_id == user.id,
|
||||
Subscription.hidden.is_(False),
|
||||
),
|
||||
)
|
||||
)
|
||||
return {
|
||||
"channels_total": total,
|
||||
"channels_details_synced": details_synced,
|
||||
"channels_recent_synced": recent_synced,
|
||||
"channels_deep_done": deep_done,
|
||||
"channels_recent_pending": total - recent_synced,
|
||||
"channels_deep_pending": total - deep_done,
|
||||
"my_videos": my_videos or 0,
|
||||
"quota_used_today": quota.units_used_today(db),
|
||||
"quota_remaining_today": quota.remaining_today(db),
|
||||
"paused": state.is_sync_paused(db),
|
||||
}
|
||||
|
||||
|
||||
@router.post("/pause")
|
||||
def pause_sync(
|
||||
user: User = Depends(current_user), db: Session = Depends(get_db)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue