feat(series): post→series flows + browse list — backend (FC-6.2)
The post-aware on-ramp + the data behind the missing Series browse view. - page_number_parser: conservative stated-page parser (pages 9-12 / page 5 / [3/8] / 3 of 8), keyword-gated to avoid false positives. Pure + unit-tested. - SeriesService.promote_post_to_series: a self-contained post becomes its own series — series tag named after the post, one chapter, the post's images as pages (ordered by capture order; stated pages parsed from title/description). - SeriesService.add_post_as_chapter: append a post as the next chapter of an existing series, titled after the post and slotted by parsed page number (a "pages 1-4" post lands ahead of the "pages 9-12" chapter). - SeriesService.list_series: browse cards — cover thumb, artist, chapter/page counts, gap flag, last-updated; sort recent|name|size + filter by artist. - API: GET /api/series, POST /api/series/from-post, POST /api/series/<id>/add-post. - Resolver uses ImageRecord.primary_post_id (same linkage the posts feed renders). Frontend (Add-to-series control + Series view + nav) lands next. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -597,3 +597,59 @@ async def series_chapter_reorder_pages(tag_id: int, chapter_id: int):
|
||||
return _series_err(exc)
|
||||
await session.commit()
|
||||
return jsonify({"ok": True})
|
||||
|
||||
|
||||
# ---- browse list + post→series flows (FC-6.2) -----------------------------
|
||||
|
||||
|
||||
@tags_bp.route("/series", methods=["GET"])
|
||||
async def series_list():
|
||||
args = request.args
|
||||
sort = args.get("sort", "recent")
|
||||
if sort not in ("recent", "name", "size"):
|
||||
return jsonify({"error": "sort must be recent|name|size"}), 400
|
||||
artist_id = None
|
||||
if args.get("artist_id") is not None:
|
||||
try:
|
||||
artist_id = int(args["artist_id"])
|
||||
except ValueError:
|
||||
return jsonify({"error": "artist_id must be an integer"}), 400
|
||||
async with get_session() as session:
|
||||
rows = await SeriesService(session).list_series(
|
||||
sort=sort, artist_id=artist_id
|
||||
)
|
||||
return jsonify({"series": rows})
|
||||
|
||||
|
||||
@tags_bp.route("/series/from-post", methods=["POST"])
|
||||
async def series_from_post():
|
||||
body = await request.get_json()
|
||||
post_id, err = _opt_int(body, "post_id")
|
||||
if err:
|
||||
return err
|
||||
if post_id is None:
|
||||
return jsonify({"error": "post_id required"}), 400
|
||||
async with get_session() as session:
|
||||
try:
|
||||
out = await SeriesService(session).promote_post_to_series(post_id)
|
||||
except SeriesError as exc:
|
||||
return _series_err(exc)
|
||||
await session.commit()
|
||||
return jsonify(out)
|
||||
|
||||
|
||||
@tags_bp.route("/series/<int:tag_id>/add-post", methods=["POST"])
|
||||
async def series_add_post(tag_id: int):
|
||||
body = await request.get_json()
|
||||
post_id, err = _opt_int(body, "post_id")
|
||||
if err:
|
||||
return err
|
||||
if post_id is None:
|
||||
return jsonify({"error": "post_id required"}), 400
|
||||
async with get_session() as session:
|
||||
try:
|
||||
out = await SeriesService(session).add_post_as_chapter(tag_id, post_id)
|
||||
except SeriesError as exc:
|
||||
return _series_err(exc)
|
||||
await session.commit()
|
||||
return jsonify(out)
|
||||
|
||||
Reference in New Issue
Block a user