203 lines
7.8 KiB
Python
203 lines
7.8 KiB
Python
"""
|
||
Lore notları — "lore" izni (admin + loremaster). Hikaye için küçük notlar; sonra derlenir.
|
||
Hepsi admin-only (GET dahil — özel içerik). WAF nedeniyle sadece GET/POST.
|
||
GET /lore → notlar (yeniden eskiye)
|
||
POST /lore → yeni not
|
||
POST /lore/{id} → güncelle
|
||
POST /lore/{id}/delete → sil
|
||
"""
|
||
from fastapi import APIRouter, Depends, HTTPException
|
||
from pydantic import BaseModel, Field
|
||
from sqlalchemy import text
|
||
from sqlalchemy.ext.asyncio import AsyncSession
|
||
|
||
from deps import require_perm, get_user_perms, has_perm
|
||
from models import User, get_db
|
||
|
||
router = APIRouter(prefix="/lore", tags=["lore"])
|
||
|
||
|
||
async def _assert_owner_or_admin(db: AsyncSession, user: User, table: str, row_id: int):
|
||
"""Admin (perm '*') her şeyi düzenler/siler; diğerleri sadece kendi kaydını."""
|
||
perms = await get_user_perms(user, db)
|
||
if has_perm(perms, "*"):
|
||
return
|
||
owner = (await db.execute(text(
|
||
f"SELECT author_id FROM {table} WHERE id=:id"
|
||
), {"id": row_id})).scalar_one_or_none()
|
||
if owner is None:
|
||
raise HTTPException(404, "Kayıt yok")
|
||
if owner != user.id:
|
||
raise HTTPException(403, "Sadece kendi kaydını düzenleyebilir/silebilirsin")
|
||
|
||
|
||
class NoteIn(BaseModel):
|
||
body: str = Field(min_length=1, max_length=20000)
|
||
body_en: str | None = Field(default=None, max_length=20000)
|
||
tag: str | None = Field(default=None, max_length=60)
|
||
subject_kind: str | None = Field(default=None, max_length=16) # 'npc' | 'place' | None
|
||
subject_id: int | None = None
|
||
subject_label: str | None = Field(default=None, max_length=120)
|
||
|
||
|
||
class NoteOut(BaseModel):
|
||
id: int
|
||
body: str
|
||
body_en: str | None
|
||
tag: str | None
|
||
subject_kind: str | None
|
||
subject_id: int | None
|
||
subject_label: str | None
|
||
author: str | None
|
||
author_id: int | None
|
||
created_at: str
|
||
updated_at: str
|
||
|
||
|
||
_NOTE_COLS = ("id,body,body_en,tag,subject_kind,subject_id,subject_label,"
|
||
"author,author_id,created_at,updated_at")
|
||
|
||
|
||
def _author(user: User) -> str:
|
||
return (user.display_name or "").strip() or user.email
|
||
|
||
|
||
def _row(r) -> NoteOut:
|
||
d = dict(r)
|
||
d["created_at"] = d["created_at"].isoformat()
|
||
d["updated_at"] = d["updated_at"].isoformat()
|
||
return NoteOut(**d)
|
||
|
||
|
||
@router.get("", response_model=list[NoteOut])
|
||
async def list_notes(user: User = Depends(require_perm("lore")), db: AsyncSession = Depends(get_db)):
|
||
rows = (await db.execute(text(
|
||
f"SELECT {_NOTE_COLS} FROM lore_notes ORDER BY id DESC"
|
||
))).mappings().all()
|
||
return [_row(r) for r in rows]
|
||
|
||
|
||
@router.get("/subjects")
|
||
async def list_subjects(user: User = Depends(require_perm("lore")), db: AsyncSession = Depends(get_db)):
|
||
"""Not bağlanabilecek konular: kişiler (npcs) + yerler (map_tags)."""
|
||
npcs = (await db.execute(text(
|
||
"SELECT id, name, role FROM npcs ORDER BY name"
|
||
))).mappings().all()
|
||
places = (await db.execute(text(
|
||
"SELECT id, name, category FROM map_tags ORDER BY name"
|
||
))).mappings().all()
|
||
return {
|
||
"npc": [{"id": r["id"], "label": r["name"], "sub": r["role"] or ""} for r in npcs],
|
||
"place": [{"id": r["id"], "label": r["name"], "sub": r["category"] or ""} for r in places],
|
||
}
|
||
|
||
|
||
@router.post("", response_model=NoteOut)
|
||
async def add_note(n: NoteIn, user: User = Depends(require_perm("lore")), db: AsyncSession = Depends(get_db)):
|
||
row = (await db.execute(text(
|
||
"INSERT INTO lore_notes(body,body_en,tag,subject_kind,subject_id,subject_label,author,author_id) "
|
||
"VALUES(:body,:body_en,:tag,:sk,:sid,:slabel,:author,:aid) "
|
||
f"RETURNING {_NOTE_COLS}"
|
||
), {"body": n.body, "body_en": n.body_en, "tag": n.tag, "sk": n.subject_kind,
|
||
"sid": n.subject_id, "slabel": n.subject_label,
|
||
"author": _author(user), "aid": user.id})).mappings().one()
|
||
await db.commit()
|
||
return _row(row)
|
||
|
||
|
||
# ---- Temel Hikaye — uzun metin bölümleri (lore_story) ----
|
||
# ÖNEMLİ: /lore/story yolları /lore/{note_id}'den ÖNCE tanımlı olmalı (route sırası).
|
||
|
||
class StoryIn(BaseModel):
|
||
title: str | None = Field(default=None, max_length=120)
|
||
body: str = Field(min_length=1, max_length=200000)
|
||
body_en: str | None = Field(default=None, max_length=200000)
|
||
|
||
|
||
class StoryOut(BaseModel):
|
||
id: int
|
||
title: str | None
|
||
body: str
|
||
body_en: str | None
|
||
author: str | None
|
||
author_id: int | None
|
||
created_at: str
|
||
updated_at: str
|
||
|
||
|
||
def _srow(r) -> StoryOut:
|
||
d = dict(r)
|
||
d["created_at"] = d["created_at"].isoformat()
|
||
d["updated_at"] = d["updated_at"].isoformat()
|
||
return StoryOut(**d)
|
||
|
||
|
||
@router.get("/story", response_model=list[StoryOut])
|
||
async def list_story(user: User = Depends(require_perm("lore")), db: AsyncSession = Depends(get_db)):
|
||
rows = (await db.execute(text(
|
||
"SELECT id,title,body,body_en,author,author_id,created_at,updated_at FROM lore_story ORDER BY sort_order,id"
|
||
))).mappings().all()
|
||
return [_srow(r) for r in rows]
|
||
|
||
|
||
@router.post("/story", response_model=StoryOut)
|
||
async def add_story(s: StoryIn, user: User = Depends(require_perm("lore")),
|
||
db: AsyncSession = Depends(get_db)):
|
||
row = (await db.execute(text(
|
||
"INSERT INTO lore_story(title,body,body_en,author,author_id) VALUES(:title,:body,:body_en,:author,:aid) "
|
||
"RETURNING id,title,body,body_en,author,author_id,created_at,updated_at"
|
||
), {"title": s.title, "body": s.body, "body_en": s.body_en, "author": _author(user), "aid": user.id})).mappings().one()
|
||
await db.commit()
|
||
return _srow(row)
|
||
|
||
|
||
@router.post("/story/{story_id}", response_model=StoryOut)
|
||
async def update_story(story_id: int, s: StoryIn, user: User = Depends(require_perm("lore")),
|
||
db: AsyncSession = Depends(get_db)):
|
||
await _assert_owner_or_admin(db, user, "lore_story", story_id)
|
||
row = (await db.execute(text(
|
||
"UPDATE lore_story SET title=:title,body=:body,body_en=:body_en,updated_at=now() WHERE id=:id "
|
||
"RETURNING id,title,body,body_en,author,author_id,created_at,updated_at"
|
||
), {"id": story_id, "title": s.title, "body": s.body, "body_en": s.body_en})).mappings().one_or_none()
|
||
if not row:
|
||
raise HTTPException(404, "Bölüm yok")
|
||
await db.commit()
|
||
return _srow(row)
|
||
|
||
|
||
@router.post("/story/{story_id}/delete")
|
||
async def delete_story(story_id: int, user: User = Depends(require_perm("lore")),
|
||
db: AsyncSession = Depends(get_db)):
|
||
await _assert_owner_or_admin(db, user, "lore_story", story_id)
|
||
res = await db.execute(text("DELETE FROM lore_story WHERE id=:id"), {"id": story_id})
|
||
await db.commit()
|
||
if res.rowcount == 0:
|
||
raise HTTPException(404, "Bölüm yok")
|
||
return {"deleted": story_id}
|
||
|
||
|
||
@router.post("/{note_id}", response_model=NoteOut)
|
||
async def update_note(note_id: int, n: NoteIn, user: User = Depends(require_perm("lore")),
|
||
db: AsyncSession = Depends(get_db)):
|
||
await _assert_owner_or_admin(db, user, "lore_notes", note_id)
|
||
row = (await db.execute(text(
|
||
"UPDATE lore_notes SET body=:body,body_en=:body_en,tag=:tag,"
|
||
"subject_kind=:sk,subject_id=:sid,subject_label=:slabel,updated_at=now() WHERE id=:id "
|
||
f"RETURNING {_NOTE_COLS}"
|
||
), {"id": note_id, "body": n.body, "body_en": n.body_en, "tag": n.tag,
|
||
"sk": n.subject_kind, "sid": n.subject_id, "slabel": n.subject_label})).mappings().one_or_none()
|
||
if not row:
|
||
raise HTTPException(404, "Not yok")
|
||
await db.commit()
|
||
return _row(row)
|
||
|
||
|
||
@router.post("/{note_id}/delete")
|
||
async def delete_note(note_id: int, user: User = Depends(require_perm("lore")),
|
||
db: AsyncSession = Depends(get_db)):
|
||
await _assert_owner_or_admin(db, user, "lore_notes", note_id)
|
||
res = await db.execute(text("DELETE FROM lore_notes WHERE id=:id"), {"id": note_id})
|
||
await db.commit()
|
||
if res.rowcount == 0:
|
||
raise HTTPException(404, "Not yok")
|
||
return {"deleted": note_id}
|