Admin Panel: - User Detail Modal with full profile info - Bulk tier upgrade for multiple users - User export to CSV - Price Alerts overview tab - Domain Health Check trigger - Email Test functionality - Scheduler Status with job info and last runs - Activity Log for admin actions - Blog management tab with CRUD Blog System: - BlogPost model with full content management - Public API: list, featured, categories, single post - Admin API: create, update, delete, publish/unpublish - Frontend blog listing page with categories - Frontend blog detail page with styling - View count tracking OAuth: - Google OAuth integration - GitHub OAuth integration - OAuth callback handling - Provider selection on login/register Other improvements: - Domain checker with check_all_domains function - Admin activity logging - Breadcrumbs component - Toast notification component - Various UI/UX improvements
75 lines
2.5 KiB
Python
75 lines
2.5 KiB
Python
"""
|
|
Blog Post Model.
|
|
|
|
Stores blog articles for the pounce platform.
|
|
"""
|
|
from datetime import datetime
|
|
from sqlalchemy import Column, Integer, String, Text, DateTime, Boolean, ForeignKey
|
|
from sqlalchemy.orm import relationship
|
|
|
|
from app.database import Base
|
|
|
|
|
|
class BlogPost(Base):
|
|
"""Model for blog posts."""
|
|
__tablename__ = "blog_posts"
|
|
|
|
id = Column(Integer, primary_key=True, index=True)
|
|
|
|
# Content
|
|
title = Column(String(255), nullable=False)
|
|
slug = Column(String(255), unique=True, nullable=False, index=True)
|
|
excerpt = Column(Text, nullable=True) # Short summary for listings
|
|
content = Column(Text, nullable=False) # Full markdown/HTML content
|
|
|
|
# Meta
|
|
cover_image = Column(String(500), nullable=True) # URL to cover image
|
|
category = Column(String(100), nullable=True) # e.g., "Domain Tips", "Industry News"
|
|
tags = Column(String(500), nullable=True) # Comma-separated tags
|
|
|
|
# SEO
|
|
meta_title = Column(String(255), nullable=True)
|
|
meta_description = Column(String(500), nullable=True)
|
|
|
|
# Status
|
|
is_published = Column(Boolean, default=False)
|
|
published_at = Column(DateTime, nullable=True)
|
|
|
|
# Author
|
|
author_id = Column(Integer, ForeignKey("users.id"), nullable=False)
|
|
author = relationship("User", backref="blog_posts")
|
|
|
|
# Timestamps
|
|
created_at = Column(DateTime, default=datetime.utcnow)
|
|
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
|
|
|
# Stats
|
|
view_count = Column(Integer, default=0)
|
|
|
|
def to_dict(self, include_content: bool = True) -> dict:
|
|
"""Convert to dictionary."""
|
|
data = {
|
|
"id": self.id,
|
|
"title": self.title,
|
|
"slug": self.slug,
|
|
"excerpt": self.excerpt,
|
|
"cover_image": self.cover_image,
|
|
"category": self.category,
|
|
"tags": self.tags.split(",") if self.tags else [],
|
|
"is_published": self.is_published,
|
|
"published_at": self.published_at.isoformat() if self.published_at else None,
|
|
"created_at": self.created_at.isoformat(),
|
|
"updated_at": self.updated_at.isoformat(),
|
|
"view_count": self.view_count,
|
|
"author": {
|
|
"id": self.author_id,
|
|
"name": self.author.name if self.author else None,
|
|
}
|
|
}
|
|
if include_content:
|
|
data["content"] = self.content
|
|
data["meta_title"] = self.meta_title
|
|
data["meta_description"] = self.meta_description
|
|
return data
|
|
|