blog.py 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156
  1. from fastapi import APIRouter, Depends, HTTPException, Request
  2. from typing import List, Optional
  3. from pydantic import BaseModel
  4. from datetime import datetime
  5. import db
  6. import mysql.connector
  7. import auth_utils
  8. from services.audit_service import audit_service
  9. router = APIRouter(prefix="/blog", tags=["blog"])
  10. class PostBase(BaseModel):
  11. slug: str
  12. title_en: str
  13. title_me: Optional[str] = None
  14. title_ru: Optional[str] = None
  15. title_ua: Optional[str] = None
  16. excerpt_en: Optional[str] = None
  17. excerpt_me: Optional[str] = None
  18. excerpt_ru: Optional[str] = None
  19. excerpt_ua: Optional[str] = None
  20. content_en: str
  21. content_me: Optional[str] = None
  22. content_ru: Optional[str] = None
  23. content_ua: Optional[str] = None
  24. category: Optional[str] = None
  25. image_url: Optional[str] = None
  26. is_published: bool = False
  27. class PostCreate(PostBase):
  28. pass
  29. class PostUpdate(PostBase):
  30. pass
  31. class Post(PostBase):
  32. id: int
  33. created_at: datetime
  34. updated_at: datetime
  35. class Config:
  36. from_attributes = True
  37. @router.get("/", response_model=List[Post])
  38. async def get_posts(published_only: bool = True):
  39. query = "SELECT * FROM posts"
  40. if published_only:
  41. query += " WHERE is_published = TRUE"
  42. query += " ORDER BY created_at DESC"
  43. return db.execute_query(query)
  44. @router.get("/{id_or_slug}", response_model=Post)
  45. async def get_post(id_or_slug: str):
  46. # Try by slug first
  47. res = db.execute_query("SELECT * FROM posts WHERE slug = %s", (id_or_slug,))
  48. if res: return res[0]
  49. # If not found, try by ID if it looks like an int
  50. if id_or_slug.isdigit():
  51. res = db.execute_query("SELECT * FROM posts WHERE id = %s", (int(id_or_slug),))
  52. if res: return res[0]
  53. raise HTTPException(status_code=404, detail="Post not found")
  54. @router.post("/", response_model=Post)
  55. async def create_post(post: PostCreate, request: Request, token: str = Depends(auth_utils.oauth2_scheme)):
  56. payload = auth_utils.decode_token(token)
  57. if not payload or payload.get("role") != 'admin':
  58. raise HTTPException(status_code=403, detail="Admin role required")
  59. query = """
  60. INSERT INTO posts (
  61. slug, title_en, title_me, title_ru, title_ua,
  62. excerpt_en, excerpt_me, excerpt_ru, excerpt_ua,
  63. content_en, content_me, content_ru, content_ua,
  64. category, image_url, is_published
  65. ) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
  66. """
  67. values = (
  68. post.slug, post.title_en, post.title_me, post.title_ru, post.title_ua,
  69. post.excerpt_en, post.excerpt_me, post.excerpt_ru, post.excerpt_ua,
  70. post.content_en, post.content_me, post.content_ru, post.content_ua,
  71. post.category, post.image_url, post.is_published
  72. )
  73. try:
  74. new_id = db.execute_commit(query, values)
  75. res = db.execute_query("SELECT * FROM posts WHERE id = %s", (new_id,))
  76. await audit_service.log(
  77. user_id=payload.get("id"),
  78. action="create_blog_post",
  79. target_type="blog_post",
  80. target_id=new_id,
  81. details={"slug": post.slug, "title": post.title_en},
  82. request=request
  83. )
  84. return res[0]
  85. except mysql.connector.Error as err:
  86. raise HTTPException(status_code=400, detail=str(err))
  87. @router.put("/{post_id}", response_model=Post)
  88. async def update_post(post_id: int, post: PostUpdate, request: Request, token: str = Depends(auth_utils.oauth2_scheme)):
  89. payload = auth_utils.decode_token(token)
  90. if not payload or payload.get("role") != 'admin':
  91. raise HTTPException(status_code=403, detail="Admin role required")
  92. query = """
  93. UPDATE posts SET
  94. slug=%s, title_en=%s, title_me=%s, title_ru=%s, title_ua=%s,
  95. excerpt_en=%s, excerpt_me=%s, excerpt_ru=%s, excerpt_ua=%s,
  96. content_en=%s, content_me=%s, content_ru=%s, content_ua=%s,
  97. category=%s, image_url=%s, is_published=%s
  98. WHERE id = %s
  99. """
  100. values = (
  101. post.slug, post.title_en, post.title_me, post.title_ru, post.title_ua,
  102. post.excerpt_en, post.excerpt_me, post.excerpt_ru, post.excerpt_ua,
  103. post.content_en, post.content_me, post.content_ru, post.content_ua,
  104. post.category, post.image_url, post.is_published, post_id
  105. )
  106. db.execute_commit(query, values)
  107. res = db.execute_query("SELECT * FROM posts WHERE id = %s", (post_id,))
  108. if not res:
  109. raise HTTPException(status_code=404, detail="Post not found")
  110. await audit_service.log(
  111. user_id=payload.get("id"),
  112. action="update_blog_post",
  113. target_type="blog_post",
  114. target_id=post_id,
  115. details={"slug": post.slug, "title": post.title_en},
  116. request=request
  117. )
  118. return res[0]
  119. @router.delete("/{post_id}")
  120. async def delete_post(post_id: int, request: Request, token: str = Depends(auth_utils.oauth2_scheme)):
  121. payload = auth_utils.decode_token(token)
  122. if not payload or payload.get("role") != 'admin':
  123. raise HTTPException(status_code=403, detail="Admin role required")
  124. # We don't easily get rowcount from execute_commit as I wrote it,
  125. # but we can check existence first or modify execute_commit.
  126. # For now, let's just execute it.
  127. db.execute_commit("DELETE FROM posts WHERE id = %s", (post_id,))
  128. await audit_service.log(
  129. user_id=payload.get("id"),
  130. action="delete_blog_post",
  131. target_type="blog_post",
  132. target_id=post_id,
  133. request=request
  134. )
  135. return {"message": "Post deleted successfully"}