feat(channels): discover & subscribe to channels from playlists
Add a "Discover from playlists" tab to the Channel manager that lists
channels appearing in the user's playlists they don't subscribe to, with
a one-click Subscribe.
- GET /api/channels/discovery: local join (playlist_items -> videos ->
channels) minus the user's subscriptions and their own channel. Enriches
stub channels' metadata up front (title/thumbnail/subscriber count via the
API key) so the user can judge a channel before subscribing; videos are
not pulled (the scheduler picks those up).
- POST /api/channels/{id}/subscribe: write-scope gated, subscriptions.insert
+ local Subscription with the returned resource id.
- YouTubeClient.insert_subscription / get_my_channel_id.
- users.yt_channel_id (migration 0019) caches the user's own channel id so
discovery can exclude it.
- Frontend: ChannelDiscovery DataTable, Channels tab toggle (persisted),
api methods, trilingual strings. Subscribe ships a typed notification
payload (ChannelSubscribedMeta) for the inbox to act on.
This commit is contained in:
parent
b5141ab112
commit
fb6f0c5dcb
11 changed files with 517 additions and 4 deletions
28
backend/alembic/versions/0019_user_yt_channel_id.py
Normal file
28
backend/alembic/versions/0019_user_yt_channel_id.py
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
"""cache each user's own YouTube channel id
|
||||
|
||||
Revision ID: 0019_user_yt_channel_id
|
||||
Revises: 0018_maintenance_batch_setting
|
||||
Create Date: 2026-06-19
|
||||
|
||||
Adds users.yt_channel_id: the user's own YouTube channel id, resolved lazily from
|
||||
channels.list?mine=true and cached. Used to exclude the user's own channel from the
|
||||
playlist-discovery list (you don't subscribe to yourself). NULL = not resolved yet.
|
||||
Purely additive.
|
||||
"""
|
||||
from typing import Sequence, Union
|
||||
|
||||
import sqlalchemy as sa
|
||||
from alembic import op
|
||||
|
||||
revision: str = "0019_user_yt_channel_id"
|
||||
down_revision: Union[str, None] = "0018_maintenance_batch_setting"
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
op.add_column("users", sa.Column("yt_channel_id", sa.String(length=32), nullable=True))
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.drop_column("users", "yt_channel_id")
|
||||
Loading…
Add table
Add a link
Reference in a new issue