"""스니펫 API — 프론트 `features/snippets/api/snippets.api.ts` 5개 호출과 1:1. 옛 Tauri 브릿지 규칙 그대로. GET /api/v1/snippets → Snippet[] (내 usage 합쳐서) POST /api/v1/snippets {name,desc,body,category} → Snippet (이름 중복이면 409) PUT /api/v1/snippets/{name} {desc,body,category} → Snippet DELETE /api/v1/snippets/{name} → {name} POST /api/v1/snippets/{name}/use → {name, usageCount, lastUsed} """ from django.db import IntegrityError, transaction from django.shortcuts import get_object_or_404 from django.utils import timezone from rest_framework.response import Response from rest_framework.views import APIView from common.envelope import CodedError from .models import Snippet, SnippetUsage def normalize_name(name: str) -> str: return (name or "").strip().upper().replace(" ", "_") def _validate(body: dict, *, need_name: bool) -> dict: name = normalize_name(body.get("name", "")) if need_name and not name: raise CodedError(400, "VALIDATION_ERROR", "이름을 입력해야 함") if not (body.get("body") or "").strip(): raise CodedError(400, "VALIDATION_ERROR", "내용을 입력해야 함") return { "name": name, "desc": body.get("desc") or "", "body": body["body"], "category": (body.get("category") or "").strip() or "코드", } class SnippetListView(APIView): def get(self, request): usage = {u.snippet_id: u for u in SnippetUsage.objects.filter(user=request.user)} return Response([s.as_dto(usage.get(s.name)) for s in Snippet.objects.all()]) def post(self, request): d = _validate(request.data, need_name=True) try: with transaction.atomic(): # 중복 INSERT 실패가 바깥 트랜잭션을 깨지 않게(savepoint) s = Snippet.objects.create(**d, created_by=request.user) except IntegrityError: raise CodedError(409, "CONFLICT", f"이미 있는 이름임: {d['name']}") return Response(s.as_dto(), status=201) class SnippetDetailView(APIView): def put(self, request, name: str): s = get_object_or_404(Snippet, name=normalize_name(name)) d = _validate({**request.data, "name": s.name}, need_name=False) s.desc, s.body, s.category = d["desc"], d["body"], d["category"] s.save(update_fields=["desc", "body", "category", "updated_at"]) usage = SnippetUsage.objects.filter(snippet=s, user=request.user).first() return Response(s.as_dto(usage)) def delete(self, request, name: str): s = get_object_or_404(Snippet, name=normalize_name(name)) deleted = s.name # delete() 가 pk 를 None 으로 지움 s.delete() return Response({"name": deleted}) class SnippetUseView(APIView): def post(self, request, name: str): s = get_object_or_404(Snippet, name=normalize_name(name)) u, _ = SnippetUsage.objects.get_or_create(snippet=s, user=request.user) u.count += 1 u.last_used = timezone.now() u.save(update_fields=["count", "last_used"]) return Response({"name": s.name, "usageCount": u.count, "lastUsed": int(u.last_used.timestamp())})