yves.gugger cff0ba0984 feat: Add Admin Panel enhancements, Blog system, and OAuth
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
2025-12-09 16:52:54 +01:00

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