feat(backend): add blog publishing platform
This commit is contained in:
@@ -1,22 +1,29 @@
|
||||
"""Blog API schemas."""
|
||||
|
||||
from ninja import Schema, ModelSchema
|
||||
from typing import Optional, List
|
||||
from datetime import datetime
|
||||
from typing import List, Optional
|
||||
|
||||
from apps.blog.models import Category, Tag, Comment
|
||||
from ninja import ModelSchema, Schema
|
||||
|
||||
from apps.blog.models import Category, Comment, PostAsset, Tag
|
||||
from core.media import PREVIEW_VARIANT, THUMBNAIL_VARIANT, derivative_url
|
||||
|
||||
|
||||
class CategorySchema(ModelSchema):
|
||||
created_at: Optional[datetime] = None
|
||||
|
||||
class Config:
|
||||
model = Category
|
||||
model_fields = ['id', 'name', 'slug', 'description']
|
||||
model_fields = ["id", "name", "slug", "description", "created_at"]
|
||||
|
||||
|
||||
class TagSchema(ModelSchema):
|
||||
created_at: Optional[datetime] = None
|
||||
|
||||
class Config:
|
||||
model = Tag
|
||||
model_fields = ['id', 'name', 'slug']
|
||||
model_fields = ["id", "name", "slug", "created_at"]
|
||||
|
||||
|
||||
class AuthorSchema(Schema):
|
||||
id: int
|
||||
@@ -29,8 +36,8 @@ class AuthorSchema(Schema):
|
||||
|
||||
@staticmethod
|
||||
def resolve_profile_picture(obj, context):
|
||||
request = context['request']
|
||||
if obj.profile_picture and hasattr(obj.profile_picture, 'url'):
|
||||
request = context["request"]
|
||||
if obj.profile_picture and hasattr(obj.profile_picture, "url"):
|
||||
return request.build_absolute_uri(obj.profile_picture.url)
|
||||
return None
|
||||
|
||||
@@ -46,6 +53,64 @@ class AuthorSchema(Schema):
|
||||
url = derivative_url(obj.profile_picture, PREVIEW_VARIANT)
|
||||
return request.build_absolute_uri(url) if url else None
|
||||
|
||||
|
||||
class PostAssetSchema(ModelSchema):
|
||||
absolute_file_url: Optional[str] = None
|
||||
absolute_thumbnail_url: Optional[str] = None
|
||||
absolute_preview_url: Optional[str] = None
|
||||
absolute_blur_url: Optional[str] = None
|
||||
markdown_image: Optional[str] = None
|
||||
markdown_link: Optional[str] = None
|
||||
uploaded_by: AuthorSchema
|
||||
|
||||
class Config:
|
||||
model = PostAsset
|
||||
model_fields = [
|
||||
"id",
|
||||
"file_type",
|
||||
"title",
|
||||
"alt_text",
|
||||
"caption",
|
||||
"size",
|
||||
"mime_type",
|
||||
"created_at",
|
||||
]
|
||||
|
||||
@staticmethod
|
||||
def resolve_absolute_file_url(obj, context):
|
||||
request = context["request"]
|
||||
return request.build_absolute_uri(obj.file.url) if obj.file else None
|
||||
|
||||
@staticmethod
|
||||
def resolve_absolute_thumbnail_url(obj, context):
|
||||
request = context["request"]
|
||||
return request.build_absolute_uri(obj.thumbnail_url) if obj.thumbnail_url else None
|
||||
|
||||
@staticmethod
|
||||
def resolve_absolute_preview_url(obj, context):
|
||||
request = context["request"]
|
||||
return request.build_absolute_uri(obj.preview_url) if obj.preview_url else None
|
||||
|
||||
@staticmethod
|
||||
def resolve_absolute_blur_url(obj, context):
|
||||
request = context["request"]
|
||||
return request.build_absolute_uri(obj.blur_url) if obj.blur_url else None
|
||||
|
||||
@staticmethod
|
||||
def resolve_markdown_image(obj, context):
|
||||
request = context["request"]
|
||||
if obj.file_type != PostAsset.FileType.IMAGE or not obj.file:
|
||||
return None
|
||||
return f"})"
|
||||
|
||||
@staticmethod
|
||||
def resolve_markdown_link(obj, context):
|
||||
request = context["request"]
|
||||
if not obj.file:
|
||||
return None
|
||||
return f"[{obj.title}]({request.build_absolute_uri(obj.file.url)})"
|
||||
|
||||
|
||||
class PostListSchema(Schema):
|
||||
id: int
|
||||
title: str
|
||||
@@ -62,7 +127,18 @@ class PostListSchema(Schema):
|
||||
tags: List[TagSchema]
|
||||
is_featured: bool
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
reading_time: int
|
||||
seo_title: str
|
||||
seo_description: str
|
||||
canonical_url: str
|
||||
og_title: str
|
||||
og_description: str
|
||||
noindex: bool
|
||||
focus_keyword: str
|
||||
likes_count: int
|
||||
saves_count: int
|
||||
comments_count: int
|
||||
|
||||
@staticmethod
|
||||
def resolve_absolute_featured_image_url(obj, context):
|
||||
@@ -83,9 +159,32 @@ class PostListSchema(Schema):
|
||||
url = derivative_url(obj.featured_image, PREVIEW_VARIANT)
|
||||
return request.build_absolute_uri(url) if url else None
|
||||
|
||||
@staticmethod
|
||||
def resolve_likes_count(obj):
|
||||
return getattr(obj, "likes_count", None) or obj.likes.count()
|
||||
|
||||
@staticmethod
|
||||
def resolve_saves_count(obj):
|
||||
return getattr(obj, "saves_count", None) or obj.saves.count()
|
||||
|
||||
@staticmethod
|
||||
def resolve_comments_count(obj):
|
||||
return getattr(obj, "comments_count", None) or obj.comments.filter(is_approved=True).count()
|
||||
|
||||
|
||||
class PostDetailSchema(PostListSchema):
|
||||
content: str
|
||||
content_html: str
|
||||
og_image_url: Optional[str] = None
|
||||
assets: List[PostAssetSchema] = []
|
||||
|
||||
@staticmethod
|
||||
def resolve_og_image_url(obj, context):
|
||||
request = context["request"]
|
||||
if obj.og_image and hasattr(obj.og_image, "url"):
|
||||
return request.build_absolute_uri(obj.og_image.url)
|
||||
return None
|
||||
|
||||
|
||||
class PostCreateSchema(Schema):
|
||||
title: str
|
||||
@@ -95,17 +194,37 @@ class PostCreateSchema(Schema):
|
||||
tag_ids: Optional[List[int]] = []
|
||||
status: str = "draft"
|
||||
is_featured: bool = False
|
||||
seo_title: Optional[str] = ""
|
||||
seo_description: Optional[str] = ""
|
||||
canonical_url: Optional[str] = ""
|
||||
og_title: Optional[str] = ""
|
||||
og_description: Optional[str] = ""
|
||||
noindex: Optional[bool] = False
|
||||
focus_keyword: Optional[str] = ""
|
||||
|
||||
|
||||
class PostReviewSchema(Schema):
|
||||
action: str
|
||||
note: Optional[str] = ""
|
||||
|
||||
|
||||
class PostAssetCreateSchema(Schema):
|
||||
title: Optional[str] = ""
|
||||
alt_text: Optional[str] = ""
|
||||
caption: Optional[str] = ""
|
||||
|
||||
|
||||
class CommentSchema(ModelSchema):
|
||||
author: AuthorSchema
|
||||
replies: List['CommentSchema'] = []
|
||||
replies: List["CommentSchema"] = []
|
||||
post_id: int
|
||||
post_title: str
|
||||
post_slug: str
|
||||
parent_id: Optional[int] = None
|
||||
|
||||
class Config:
|
||||
model = Comment
|
||||
model_fields = ['id', 'content', 'created_at', 'is_approved']
|
||||
model_fields = ["id", "content", "created_at", "is_approved", "hidden_at"]
|
||||
|
||||
@staticmethod
|
||||
def resolve_post_id(obj):
|
||||
@@ -119,6 +238,30 @@ class CommentSchema(ModelSchema):
|
||||
def resolve_post_slug(obj):
|
||||
return obj.post.slug
|
||||
|
||||
@staticmethod
|
||||
def resolve_parent_id(obj):
|
||||
return obj.parent_id
|
||||
|
||||
|
||||
class CommentCreateSchema(Schema):
|
||||
content: str
|
||||
parent_id: Optional[int] = None
|
||||
|
||||
|
||||
class CommentHideSchema(Schema):
|
||||
note: Optional[str] = ""
|
||||
|
||||
|
||||
class BlogInteractionSchema(Schema):
|
||||
liked: bool
|
||||
saved: bool
|
||||
likes_count: int
|
||||
saves_count: int
|
||||
comments_count: int
|
||||
|
||||
|
||||
class BlogProfileActivitySchema(Schema):
|
||||
liked_posts: List[PostListSchema]
|
||||
saved_posts: List[PostListSchema]
|
||||
comments: List[CommentSchema]
|
||||
replies: List[CommentSchema]
|
||||
|
||||
Reference in New Issue
Block a user