Skip to content

Commit a0bf878

Browse files
EstrellaXDclaude
andcommitted
feat(calendar): add drag-and-drop to assign unknown bangumi to weekdays
Allow users to drag bangumi cards from the "Unknown" section into weekday columns in the calendar view. Manual assignments are locked so calendar refresh from Bangumi.tv doesn't overwrite them. A reset button lets users unlock and send cards back to Unknown. Backend: - Add weekday_locked field to Bangumi model (migration v9) - Add PATCH /api/v1/bangumi/{id}/weekday endpoint - Skip locked items in refresh_calendar() Frontend: - Add vuedraggable for smooth drag-and-drop - Pin indicator and unpin button on manually-assigned cards - Drop zone highlighting during drag - i18n strings for drag/pin/unpin Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 326d31d commit a0bf878

15 files changed

Lines changed: 774 additions & 81 deletions

File tree

backend/src/module/api/bangumi.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,10 @@ class OffsetSuggestionDetail(BaseModel):
4141
confidence: Literal["high", "medium", "low"]
4242

4343

44+
class SetWeekdayRequest(BaseModel):
45+
weekday: Optional[int] = None # 0-6 for Mon-Sun, None to reset
46+
47+
4448
class DetectOffsetRequest(BaseModel):
4549
"""Request body for detect-offset endpoint."""
4650
title: str
@@ -339,3 +343,41 @@ async def get_needs_review():
339343
"""Get all bangumi that need review for offset mismatch."""
340344
with Database() as db:
341345
return db.bangumi.get_needs_review()
346+
347+
348+
@router.patch(
349+
path="/{bangumi_id}/weekday",
350+
response_model=APIResponse,
351+
dependencies=[Depends(get_current_user)],
352+
)
353+
async def set_weekday(bangumi_id: int, request: SetWeekdayRequest):
354+
"""Manually set the broadcast weekday for a bangumi."""
355+
if request.weekday is not None and not (0 <= request.weekday <= 6):
356+
return JSONResponse(
357+
status_code=400,
358+
content={
359+
"status": False,
360+
"msg_en": "Weekday must be 0-6 (Mon-Sun) or null.",
361+
"msg_zh": "星期必须是 0-6(周一至周日)或空。",
362+
},
363+
)
364+
with Database() as db:
365+
success = db.bangumi.set_weekday(bangumi_id, request.weekday)
366+
if success:
367+
action = f"weekday {request.weekday}" if request.weekday is not None else "unknown"
368+
return JSONResponse(
369+
status_code=200,
370+
content={
371+
"status": True,
372+
"msg_en": f"Set bangumi to {action}.",
373+
"msg_zh": f"已设置放送日为 {action}。",
374+
},
375+
)
376+
return JSONResponse(
377+
status_code=404,
378+
content={
379+
"status": False,
380+
"msg_en": f"Bangumi {bangumi_id} not found.",
381+
"msg_zh": f"未找到番剧 {bangumi_id}。",
382+
},
383+
)

backend/src/module/database/bangumi.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -657,3 +657,25 @@ def clear_needs_review(self, _id: int) -> bool:
657657
_invalidate_bangumi_cache()
658658
logger.debug("[Database] Cleared needs_review for bangumi id %s", _id)
659659
return True
660+
661+
def set_weekday(self, _id: int, weekday: int | None) -> bool:
662+
"""Set air_weekday and weekday_locked for manual calendar assignment."""
663+
bangumi = self.session.get(Bangumi, _id)
664+
if not bangumi:
665+
return False
666+
if weekday is not None:
667+
bangumi.air_weekday = weekday
668+
bangumi.weekday_locked = True
669+
else:
670+
bangumi.air_weekday = None
671+
bangumi.weekday_locked = False
672+
self.session.add(bangumi)
673+
self.session.commit()
674+
_invalidate_bangumi_cache()
675+
logger.debug(
676+
"[Database] Set weekday=%s, locked=%s for bangumi id %s",
677+
weekday,
678+
bangumi.weekday_locked,
679+
_id,
680+
)
681+
return True

backend/src/module/database/combine.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
TABLE_MODELS: list[type[SQLModel]] = [Bangumi, RSSItem, Torrent, User, Passkey]
2424

2525
# Increment this when adding new migrations to MIGRATIONS list.
26-
CURRENT_SCHEMA_VERSION = 8
26+
CURRENT_SCHEMA_VERSION = 9
2727

2828
# Each migration is a tuple of (version, description, list of SQL statements).
2929
# Migrations are applied in order. A migration at index i brings the schema
@@ -103,6 +103,13 @@
103103
"ALTER TABLE bangumi ADD COLUMN title_aliases TEXT DEFAULT NULL",
104104
],
105105
),
106+
(
107+
9,
108+
"add weekday_locked column to bangumi",
109+
[
110+
"ALTER TABLE bangumi ADD COLUMN weekday_locked BOOLEAN DEFAULT 0",
111+
],
112+
),
106113
]
107114

108115

@@ -198,6 +205,10 @@ def run_migrations(self):
198205
columns = [col["name"] for col in inspector.get_columns("bangumi")]
199206
if "title_aliases" in columns:
200207
needs_run = False
208+
if "bangumi" in tables and version == 9:
209+
columns = [col["name"] for col in inspector.get_columns("bangumi")]
210+
if "weekday_locked" in columns:
211+
needs_run = False
201212
if needs_run:
202213
try:
203214
with self.engine.connect() as conn:

backend/src/module/manager/torrent.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,7 @@ async def refresh_calendar(self):
210210
bangumis = self.bangumi.search_all()
211211
updated = 0
212212
for bangumi in bangumis:
213-
if bangumi.deleted:
213+
if bangumi.deleted or bangumi.weekday_locked:
214214
continue
215215
weekday = match_weekday(
216216
bangumi.official_title, bangumi.title_raw, calendar_items

backend/src/module/models/bangumi.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@ class Bangumi(SQLModel, table=True):
3636
air_weekday: Optional[int] = Field(
3737
default=None, alias="air_weekday", title="放送星期"
3838
)
39+
weekday_locked: bool = Field(
40+
default=False, alias="weekday_locked", title="放送星期锁定"
41+
)
3942
needs_review: bool = Field(default=False, alias="needs_review", title="需要检查")
4043
needs_review_reason: Optional[str] = Field(
4144
default=None, alias="needs_review_reason", title="检查原因"
@@ -77,6 +80,9 @@ class BangumiUpdate(SQLModel):
7780
air_weekday: Optional[int] = Field(
7881
default=None, alias="air_weekday", title="放送星期"
7982
)
83+
weekday_locked: bool = Field(
84+
default=False, alias="weekday_locked", title="放送星期锁定"
85+
)
8086
needs_review: bool = Field(default=False, alias="needs_review", title="需要检查")
8187
needs_review_reason: Optional[str] = Field(
8288
default=None, alias="needs_review_reason", title="检查原因"

0 commit comments

Comments
 (0)