fixed strapi webhook endpoint
This commit is contained in:
parent
4ccb65ba88
commit
2ff76ffda5
276
ADMINISTRATOR_GUIDE.md
Normal file
276
ADMINISTRATOR_GUIDE.md
Normal file
@ -0,0 +1,276 @@
|
|||||||
|
# Administrator Guide - Placebo.mk
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
Placebo.mk is a Macedonian news site with a sarcastic tone. This guide covers system administration, user management, and technical operations.
|
||||||
|
|
||||||
|
## System Architecture
|
||||||
|
- **Frontend**: TanStack (React 19, Query, Router) + Vite + Tailwind CSS
|
||||||
|
- **Backend**: NestJS + TypeORM + SQLite
|
||||||
|
- **CMS**: Strapi for content management
|
||||||
|
- **Database**: SQLite with TypeORM entities
|
||||||
|
|
||||||
|
## Environment Setup
|
||||||
|
|
||||||
|
### Backend Configuration
|
||||||
|
```bash
|
||||||
|
cd backend
|
||||||
|
cp .env.example .env
|
||||||
|
# Configure environment variables:
|
||||||
|
# DATABASE_PATH=./data/app.db
|
||||||
|
# PORT=3000
|
||||||
|
# NODE_ENV=development
|
||||||
|
npm install
|
||||||
|
npm run start:dev
|
||||||
|
```
|
||||||
|
|
||||||
|
### Frontend Configuration
|
||||||
|
```bash
|
||||||
|
cd frontend
|
||||||
|
cp .env.example .env
|
||||||
|
# Configure environment variables:
|
||||||
|
# VITE_API_URL=http://localhost:3000
|
||||||
|
npm install
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
### CMS (Strapi) Configuration
|
||||||
|
```bash
|
||||||
|
cd cms/cms
|
||||||
|
cp .env.example .env
|
||||||
|
# Configure Strapi environment variables:
|
||||||
|
# DATABASE_CLIENT=sqlite
|
||||||
|
# DATABASE_FILENAME=./data/app.db
|
||||||
|
# API_TOKEN_SALT=
|
||||||
|
# ADMIN_JWT_SECRET=
|
||||||
|
npm run develop
|
||||||
|
```
|
||||||
|
|
||||||
|
## Database Management
|
||||||
|
|
||||||
|
### Entities Overview
|
||||||
|
- **Articles**: News articles with status (draft/published/archived)
|
||||||
|
- **Authors**: Writer profiles with permissions
|
||||||
|
- **Categories**: Hierarchical content organization
|
||||||
|
- **Live Blogs**: Real-time event coverage
|
||||||
|
- **Live Blog Updates**: Individual updates within live blogs
|
||||||
|
|
||||||
|
### Database Operations
|
||||||
|
```bash
|
||||||
|
# Run migrations (if implemented)
|
||||||
|
npm run migration:run
|
||||||
|
|
||||||
|
# Check database schema
|
||||||
|
npm run schema:sync
|
||||||
|
|
||||||
|
# Backup database
|
||||||
|
cp backend/data/app.db backend/data/backup-$(date +%Y%m%d).db
|
||||||
|
```
|
||||||
|
|
||||||
|
## User Management
|
||||||
|
|
||||||
|
### Author Management
|
||||||
|
Authors are stored in the `authors` table with:
|
||||||
|
- Basic profile (name, bio, avatar)
|
||||||
|
- Unique slug for URLs
|
||||||
|
- `isActive` flag for permissions
|
||||||
|
- Relations to articles and live blogs
|
||||||
|
|
||||||
|
### CMS User Roles
|
||||||
|
1. **Super Admin**: Full system access
|
||||||
|
2. **Admin**: Content and user management
|
||||||
|
3. **Editor**: Content creation and editing
|
||||||
|
4. **Author**: Limited to assigned content
|
||||||
|
|
||||||
|
### Creating New Authors
|
||||||
|
```sql
|
||||||
|
INSERT INTO authors (id, name, slug, bio, avatar, isActive, createdAt, updatedAt)
|
||||||
|
VALUES (
|
||||||
|
uuid(),
|
||||||
|
'Author Name',
|
||||||
|
'author-slug',
|
||||||
|
'Author bio',
|
||||||
|
'avatar-url.jpg',
|
||||||
|
true,
|
||||||
|
datetime('now'),
|
||||||
|
datetime('now')
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
## Content Management
|
||||||
|
|
||||||
|
### Article Workflow
|
||||||
|
1. **Draft**: Initial creation phase
|
||||||
|
2. **Published**: Publicly visible
|
||||||
|
3. **Archived**: No longer public but retained
|
||||||
|
|
||||||
|
### Live Blog Management
|
||||||
|
Live blogs support real-time updates:
|
||||||
|
- **Draft**: Preparation phase
|
||||||
|
- **Live**: Active event coverage
|
||||||
|
- **Ended**: Coverage complete
|
||||||
|
- **Archived**: Historical reference
|
||||||
|
|
||||||
|
## API Management
|
||||||
|
|
||||||
|
### Available Endpoints
|
||||||
|
```
|
||||||
|
GET /api/v1/articles # List articles
|
||||||
|
GET /api/v1/articles/:id # Get single article
|
||||||
|
GET /api/v1/articles/slug/:slug # Get article by slug
|
||||||
|
POST /api/v1/articles # Create article
|
||||||
|
PUT /api/v1/articles/:id # Update article
|
||||||
|
DELETE /api/v1/articles/:id # Delete article
|
||||||
|
|
||||||
|
GET /api/v1/live-blogs # List live blogs
|
||||||
|
GET /api/v1/live-blogs/:id # Get live blog
|
||||||
|
POST /api/v1/live-blogs # Create live blog
|
||||||
|
PUT /api/v1/live-blogs/:id # Update live blog
|
||||||
|
|
||||||
|
GET /api/v1/authors # List authors
|
||||||
|
GET /api/v1/categories # List categories
|
||||||
|
```
|
||||||
|
|
||||||
|
### API Authentication
|
||||||
|
Configure JWT tokens for secure API access:
|
||||||
|
```typescript
|
||||||
|
// In .env
|
||||||
|
JWT_SECRET=your-secret-key
|
||||||
|
JWT_EXPIRES_IN=24h
|
||||||
|
```
|
||||||
|
|
||||||
|
## CMS Administration
|
||||||
|
|
||||||
|
### Strapi Admin Panel
|
||||||
|
Access at: `http://localhost:1337/admin`
|
||||||
|
|
||||||
|
### Content Types Configuration
|
||||||
|
Articles support:
|
||||||
|
- Rich text content
|
||||||
|
- Multiple media files (images, files, videos, audio)
|
||||||
|
- Featured images
|
||||||
|
- Author attribution
|
||||||
|
|
||||||
|
### Media Management
|
||||||
|
- Upload limits configured in Strapi settings
|
||||||
|
- Image optimization handled automatically
|
||||||
|
- File organization in `/uploads` directory
|
||||||
|
|
||||||
|
## Performance Monitoring
|
||||||
|
|
||||||
|
### Backend Monitoring
|
||||||
|
```bash
|
||||||
|
# Check application logs
|
||||||
|
npm run start:prod
|
||||||
|
|
||||||
|
# Monitor performance
|
||||||
|
npm run start:prod -- --inspect
|
||||||
|
```
|
||||||
|
|
||||||
|
### Frontend Performance
|
||||||
|
```bash
|
||||||
|
# Build for production
|
||||||
|
cd frontend
|
||||||
|
npm run build
|
||||||
|
|
||||||
|
# Analyze bundle size
|
||||||
|
npm run build:analyze
|
||||||
|
```
|
||||||
|
|
||||||
|
## Security Considerations
|
||||||
|
|
||||||
|
### Authentication
|
||||||
|
- Implement proper JWT handling
|
||||||
|
- Use secure password hashing
|
||||||
|
- Set appropriate CORS policies
|
||||||
|
|
||||||
|
### Data Protection
|
||||||
|
- Regular database backups
|
||||||
|
- Environment variable protection
|
||||||
|
- Input validation and sanitization
|
||||||
|
|
||||||
|
### CMS Security
|
||||||
|
- Regular Strapi updates
|
||||||
|
- Role-based access control
|
||||||
|
- Media file scanning
|
||||||
|
|
||||||
|
## Backup and Recovery
|
||||||
|
|
||||||
|
### Automated Backups
|
||||||
|
```bash
|
||||||
|
# Create backup script
|
||||||
|
#!/bin/bash
|
||||||
|
DATE=$(date +%Y%m%d_%H%M%S)
|
||||||
|
mkdir -p backups
|
||||||
|
cp backend/data/app.db backups/app_$DATE.db
|
||||||
|
tar -czf backups/uploads_$DATE.tar.gz cms/cms/public/uploads/
|
||||||
|
```
|
||||||
|
|
||||||
|
### Recovery Process
|
||||||
|
1. Stop all services
|
||||||
|
2. Restore database from backup
|
||||||
|
3. Restore media files
|
||||||
|
4. Restart services
|
||||||
|
5. Verify functionality
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Common Issues
|
||||||
|
|
||||||
|
#### Database Connection Errors
|
||||||
|
```bash
|
||||||
|
# Check database file exists
|
||||||
|
ls -la backend/data/app.db
|
||||||
|
|
||||||
|
# Verify permissions
|
||||||
|
chmod 664 backend/data/app.db
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Strapi Admin Access
|
||||||
|
```bash
|
||||||
|
# Reset admin password
|
||||||
|
npm run strapi admin:reset-password
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Frontend Build Issues
|
||||||
|
```bash
|
||||||
|
# Clear cache
|
||||||
|
rm -rf node_modules package-lock.json
|
||||||
|
npm install
|
||||||
|
|
||||||
|
# Check environment variables
|
||||||
|
cat .env
|
||||||
|
```
|
||||||
|
|
||||||
|
## Maintenance Tasks
|
||||||
|
|
||||||
|
### Daily
|
||||||
|
- Monitor system logs
|
||||||
|
- Check backup completion
|
||||||
|
- Review performance metrics
|
||||||
|
|
||||||
|
### Weekly
|
||||||
|
- Update dependencies
|
||||||
|
- Review content moderation queue
|
||||||
|
- Security scan
|
||||||
|
|
||||||
|
### Monthly
|
||||||
|
- Database maintenance
|
||||||
|
- Archive old content
|
||||||
|
- Performance optimization review
|
||||||
|
|
||||||
|
## Scaling Considerations
|
||||||
|
|
||||||
|
### Database Scaling
|
||||||
|
- Consider PostgreSQL for high traffic
|
||||||
|
- Implement read replicas
|
||||||
|
- Optimize queries and indexes
|
||||||
|
|
||||||
|
### CDN Integration
|
||||||
|
- Configure CDN for static assets
|
||||||
|
- Optimize image delivery
|
||||||
|
- Implement caching strategies
|
||||||
|
|
||||||
|
### Monitoring Setup
|
||||||
|
- Application performance monitoring
|
||||||
|
- Error tracking and alerting
|
||||||
|
- User analytics and reporting
|
||||||
314
PUBLISHER_GUIDE.md
Normal file
314
PUBLISHER_GUIDE.md
Normal file
@ -0,0 +1,314 @@
|
|||||||
|
# Publisher & Content Creator Guide - Placebo.mk
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
Placebo.mk is a Macedonian news site with a sarcastic tone. This guide covers content creation, publishing workflows, and best practices for writers and editors.
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
|
||||||
|
### Accessing the CMS
|
||||||
|
1. Navigate to: `http://your-domain.com/admin`
|
||||||
|
2. Log in with your credentials
|
||||||
|
3. Select "Content Manager" from the sidebar
|
||||||
|
|
||||||
|
### Your Dashboard
|
||||||
|
- **Content Manager**: Create and edit articles
|
||||||
|
- **Media Library**: Upload and manage images/files
|
||||||
|
- **Profile**: Update your author information
|
||||||
|
|
||||||
|
## Content Creation Workflow
|
||||||
|
|
||||||
|
### 1. Planning Your Article
|
||||||
|
Before writing:
|
||||||
|
- Choose a compelling headline
|
||||||
|
- Research your topic thoroughly
|
||||||
|
- Gather supporting images/media
|
||||||
|
- Plan your article structure
|
||||||
|
|
||||||
|
### 2. Creating a New Article
|
||||||
|
|
||||||
|
#### Basic Article Setup
|
||||||
|
1. Click **+ Create new entry** in Content Manager
|
||||||
|
2. Select **Article** content type
|
||||||
|
3. Fill in required fields:
|
||||||
|
- **Title**: Catchy, SEO-friendly headline
|
||||||
|
- **Author**: Your name (auto-populated)
|
||||||
|
- **Content**: Main article body
|
||||||
|
|
||||||
|
#### Article Fields Explained
|
||||||
|
- **Title**: Maximum 100 characters, include keywords
|
||||||
|
- **Content**: Rich text editor with formatting options
|
||||||
|
- **Media**: Upload images, videos, documents
|
||||||
|
- **Featured Image**: Main article thumbnail
|
||||||
|
- **Status**: Draft → Published → Archived
|
||||||
|
|
||||||
|
### 3. Writing Best Practices
|
||||||
|
|
||||||
|
#### Headline Guidelines
|
||||||
|
- Use active voice and strong verbs
|
||||||
|
- Include location if relevant (e.g., "Скопје:")
|
||||||
|
- Keep under 100 characters
|
||||||
|
- Ask questions or make bold statements
|
||||||
|
|
||||||
|
#### Content Structure
|
||||||
|
```
|
||||||
|
1. Lead Paragraph (2-3 sentences)
|
||||||
|
- Who, what, where, when, why
|
||||||
|
- Most important information first
|
||||||
|
|
||||||
|
2. Body Paragraphs (3-5 paragraphs)
|
||||||
|
- Supporting details and quotes
|
||||||
|
- Background information
|
||||||
|
- Analysis and context
|
||||||
|
|
||||||
|
3. Conclusion (1-2 paragraphs)
|
||||||
|
- Summary or call to action
|
||||||
|
- Future implications
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Writing Style
|
||||||
|
- **Tone**: Sarcastic but informative
|
||||||
|
- **Language**: Macedonian with local context
|
||||||
|
- **Length**: 300-800 words for news, 800-1500 for features
|
||||||
|
- **Voice**: Conversational, engaging, slightly cynical
|
||||||
|
|
||||||
|
### 4. Media Integration
|
||||||
|
|
||||||
|
#### Adding Images
|
||||||
|
1. Click **Media** field in article editor
|
||||||
|
2. Click **Add new** or select from library
|
||||||
|
3. Drag and drop or browse files
|
||||||
|
4. Add alt text for accessibility
|
||||||
|
5. Choose image size and alignment
|
||||||
|
|
||||||
|
#### Image Guidelines
|
||||||
|
- **Featured Image**: 1200x630px minimum
|
||||||
|
- **Inline Images**: 800px width maximum
|
||||||
|
- **File Size**: Under 2MB per image
|
||||||
|
- **Formats**: JPG, PNG, WebP
|
||||||
|
|
||||||
|
#### Video and Audio
|
||||||
|
- Upload MP4 videos (under 100MB)
|
||||||
|
- Embed YouTube/Vimeo links
|
||||||
|
- Add MP3 audio files for interviews
|
||||||
|
|
||||||
|
## Publishing Workflow
|
||||||
|
|
||||||
|
### Article Status Management
|
||||||
|
|
||||||
|
#### Draft Status
|
||||||
|
- Initial creation phase
|
||||||
|
- Auto-saves every 30 seconds
|
||||||
|
- Only visible to you and editors
|
||||||
|
|
||||||
|
#### Review Process
|
||||||
|
1. Submit for review when ready
|
||||||
|
2. Editor reviews content and style
|
||||||
|
3. Request changes or approve
|
||||||
|
4. Editor may edit directly
|
||||||
|
|
||||||
|
#### Published Status
|
||||||
|
- Live on the website
|
||||||
|
- Publicly accessible
|
||||||
|
- Appears in category listings
|
||||||
|
- Indexed by search engines
|
||||||
|
|
||||||
|
#### Archived Status
|
||||||
|
- No longer public
|
||||||
|
- Retained for reference
|
||||||
|
- Can be republished if needed
|
||||||
|
|
||||||
|
### Publishing Best Practices
|
||||||
|
|
||||||
|
#### Before Publishing
|
||||||
|
- [ ] Proofread for spelling/grammar
|
||||||
|
- [ ] Check all facts and sources
|
||||||
|
- [ ] Test all links
|
||||||
|
- [ ] Optimize images for web
|
||||||
|
- [ ] Add relevant tags
|
||||||
|
- [ ] Set appropriate category
|
||||||
|
|
||||||
|
#### Publishing Schedule
|
||||||
|
- **Breaking News**: Publish immediately
|
||||||
|
- **Features**: Schedule for peak traffic (10:00-14:00)
|
||||||
|
- **Analysis**: Publish by 17:00 for evening readers
|
||||||
|
|
||||||
|
## Live Blogging
|
||||||
|
|
||||||
|
### When to Use Live Blogs
|
||||||
|
- Ongoing events (protests, sports, conferences)
|
||||||
|
- Developing news stories
|
||||||
|
- Election coverage
|
||||||
|
- Emergency situations
|
||||||
|
|
||||||
|
### Creating a Live Blog
|
||||||
|
1. Navigate to **Live Blogs** in Content Manager
|
||||||
|
2. Click **+ Create new entry**
|
||||||
|
3. Fill in basic information:
|
||||||
|
- **Title**: Event name + "LIVE"
|
||||||
|
- **Description**: Brief event summary
|
||||||
|
- **Status**: Set to "Live" when ready
|
||||||
|
|
||||||
|
### Live Blog Updates
|
||||||
|
Each update should include:
|
||||||
|
- **Timestamp**: Auto-generated
|
||||||
|
- **Content**: New information (1-3 sentences)
|
||||||
|
- **Author**: Your attribution
|
||||||
|
- **Media**: Relevant photos/videos
|
||||||
|
|
||||||
|
#### Update Guidelines
|
||||||
|
- Keep updates concise and factual
|
||||||
|
- Post major developments immediately
|
||||||
|
- Use quotes when available
|
||||||
|
- Add context for new followers
|
||||||
|
- Pin important updates
|
||||||
|
|
||||||
|
## Content Categories
|
||||||
|
|
||||||
|
### Available Categories
|
||||||
|
- **Политика**: Government, elections, policy
|
||||||
|
- **Економија**: Business, markets, finance
|
||||||
|
- **Друштво**: Social issues, culture, lifestyle
|
||||||
|
- **Спорт**: Sports news and events
|
||||||
|
- **Технологија**: Tech news and innovations
|
||||||
|
- **Свет**: International news
|
||||||
|
- **Култура**: Arts, entertainment, events
|
||||||
|
|
||||||
|
### Category Selection
|
||||||
|
- Choose primary category carefully
|
||||||
|
- Use tags for additional context
|
||||||
|
- Consider audience interests
|
||||||
|
- Follow editorial calendar
|
||||||
|
|
||||||
|
## SEO and Discoverability
|
||||||
|
|
||||||
|
### Title Optimization
|
||||||
|
- Include primary keywords
|
||||||
|
- Use location when relevant
|
||||||
|
- Ask questions or make statements
|
||||||
|
- Avoid clickbait (maintain credibility)
|
||||||
|
|
||||||
|
### Content SEO
|
||||||
|
- Naturally include keywords
|
||||||
|
- Use header tags (H2, H3)
|
||||||
|
- Add internal links to related articles
|
||||||
|
- Include external sources when relevant
|
||||||
|
|
||||||
|
### Meta Information
|
||||||
|
- **Excerpt**: 150-character summary
|
||||||
|
- **Featured Image**: Optimized for social sharing
|
||||||
|
- **Tags**: 3-5 relevant keywords
|
||||||
|
|
||||||
|
## Author Profile Management
|
||||||
|
|
||||||
|
### Updating Your Profile
|
||||||
|
1. Click **Profile** in sidebar
|
||||||
|
2. Update information:
|
||||||
|
- **Display Name**: Your byline name
|
||||||
|
- **Bio**: 100-word professional summary
|
||||||
|
- **Avatar**: Professional headshot
|
||||||
|
- **Social Links**: Twitter, LinkedIn, etc.
|
||||||
|
|
||||||
|
### Author Best Practices
|
||||||
|
- Maintain consistent voice across articles
|
||||||
|
- Build expertise in specific topics
|
||||||
|
- Engage with reader comments
|
||||||
|
- Share published articles on social media
|
||||||
|
|
||||||
|
## Content Moderation
|
||||||
|
|
||||||
|
### Comment Policy
|
||||||
|
- Comments are enabled on published articles
|
||||||
|
- Inappropriate content is filtered automatically
|
||||||
|
- Editors can approve/reject comments manually
|
||||||
|
|
||||||
|
### Handling Corrections
|
||||||
|
1. Mark errors immediately
|
||||||
|
2. Correct factual mistakes
|
||||||
|
3. Add correction note at top
|
||||||
|
4. Notify editor of significant changes
|
||||||
|
|
||||||
|
## Analytics and Performance
|
||||||
|
|
||||||
|
### Article Metrics
|
||||||
|
- **Views**: Total page reads
|
||||||
|
- **Engagement**: Time on page, scroll depth
|
||||||
|
- **Shares**: Social media interactions
|
||||||
|
- **Comments**: Reader engagement
|
||||||
|
|
||||||
|
### Improving Performance
|
||||||
|
- Write compelling headlines
|
||||||
|
- Use relevant, high-quality images
|
||||||
|
- Publish at optimal times
|
||||||
|
- Share on social media
|
||||||
|
- Engage with reader comments
|
||||||
|
|
||||||
|
## Tools and Shortcuts
|
||||||
|
|
||||||
|
### Editor Shortcuts
|
||||||
|
- **Ctrl+B**: Bold text
|
||||||
|
- **Ctrl+I**: Italic text
|
||||||
|
- **Ctrl+K**: Insert link
|
||||||
|
- **Ctrl+Z**: Undo
|
||||||
|
- **Ctrl+Y**: Redo
|
||||||
|
|
||||||
|
### Media Management
|
||||||
|
- Drag and drop uploads
|
||||||
|
- Bulk image optimization
|
||||||
|
- Automatic alt text suggestions
|
||||||
|
- Image cropping and resizing
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Common Issues
|
||||||
|
|
||||||
|
#### Editor Not Saving
|
||||||
|
- Check internet connection
|
||||||
|
- Refresh page and try again
|
||||||
|
- Contact administrator if persistent
|
||||||
|
|
||||||
|
#### Image Upload Errors
|
||||||
|
- Verify file size under 2MB
|
||||||
|
- Check file format (JPG, PNG, WebP)
|
||||||
|
- Clear browser cache and retry
|
||||||
|
|
||||||
|
#### Publishing Problems
|
||||||
|
- Ensure all required fields completed
|
||||||
|
- Check article status permissions
|
||||||
|
- Contact editor for approval
|
||||||
|
|
||||||
|
## Best Practices Summary
|
||||||
|
|
||||||
|
### Do's
|
||||||
|
- Write factually accurate content
|
||||||
|
- Use engaging, sarcastic tone appropriately
|
||||||
|
- Include relevant, high-quality media
|
||||||
|
- Proofread thoroughly before publishing
|
||||||
|
- Engage with reader feedback
|
||||||
|
- Follow ethical journalism standards
|
||||||
|
|
||||||
|
### Don'ts
|
||||||
|
- Publish unverified information
|
||||||
|
- Use excessive clickbait
|
||||||
|
- Ignore reader comments
|
||||||
|
- Publish without editor review
|
||||||
|
- Violate copyright laws
|
||||||
|
- Share confidential information
|
||||||
|
|
||||||
|
## Support and Resources
|
||||||
|
|
||||||
|
### Getting Help
|
||||||
|
- **Editor**: Primary contact for content questions
|
||||||
|
- **Administrator**: Technical support and system issues
|
||||||
|
- **Style Guide**: Detailed writing and formatting guidelines
|
||||||
|
|
||||||
|
### Training Resources
|
||||||
|
- CMS video tutorials
|
||||||
|
- Writing style workshops
|
||||||
|
- SEO best practices guide
|
||||||
|
- Social media sharing strategies
|
||||||
|
|
||||||
|
### Community
|
||||||
|
- Regular writer meetings
|
||||||
|
- Content planning sessions
|
||||||
|
- Feedback and improvement discussions
|
||||||
|
- Collaboration opportunities
|
||||||
@ -1,8 +1,8 @@
|
|||||||
import { Controller, Post, Body } from '@nestjs/common';
|
import { Controller, Post, Body, Logger } from '@nestjs/common';
|
||||||
import { StrapiService } from './strapi.service';
|
import { StrapiService } from './strapi.service';
|
||||||
|
|
||||||
interface WebhookBody {
|
interface WebhookBody {
|
||||||
event: 'entry.create' | 'entry.update' | 'entry.delete';
|
event: 'entry.create' | 'entry.update' | 'entry.delete' | 'entry.publish';
|
||||||
model: string;
|
model: string;
|
||||||
entry: {
|
entry: {
|
||||||
documentId: string;
|
documentId: string;
|
||||||
@ -11,17 +11,100 @@ interface WebhookBody {
|
|||||||
|
|
||||||
@Controller('webhooks/strapi')
|
@Controller('webhooks/strapi')
|
||||||
export class StrapiController {
|
export class StrapiController {
|
||||||
|
private readonly logger = new Logger(StrapiController.name);
|
||||||
|
|
||||||
constructor(private readonly strapiService: StrapiService) {}
|
constructor(private readonly strapiService: StrapiService) {}
|
||||||
|
|
||||||
@Post('article')
|
@Post('article')
|
||||||
async handleArticleWebhook(@Body() body: WebhookBody) {
|
async handleArticleWebhook(@Body() body: WebhookBody) {
|
||||||
|
this.logger.log(`Received article webhook: ${JSON.stringify(body)}`);
|
||||||
const { event, model, entry } = body;
|
const { event, model, entry } = body;
|
||||||
|
|
||||||
if (model !== 'article') {
|
if (model !== 'article') {
|
||||||
|
this.logger.warn(`Ignored: not an article, model: ${model}`);
|
||||||
return { message: 'Ignored: not an article' };
|
return { message: 'Ignored: not an article' };
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.strapiService.handleWebhook(event, entry);
|
await this.strapiService.handleWebhook(event, {
|
||||||
|
documentId: entry.documentId,
|
||||||
|
model,
|
||||||
|
});
|
||||||
|
return { message: 'Webhook processed successfully' };
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post('live-blog')
|
||||||
|
async handleLiveBlogWebhook(@Body() body: WebhookBody) {
|
||||||
|
this.logger.log(`Received live-blog webhook: ${JSON.stringify(body)}`);
|
||||||
|
const { event, model, entry } = body;
|
||||||
|
|
||||||
|
if (model !== 'live-blog') {
|
||||||
|
this.logger.warn(`Ignored: not a live blog, model: ${model}`);
|
||||||
|
return { message: 'Ignored: not a live blog' };
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.strapiService.handleWebhook(event, {
|
||||||
|
documentId: entry.documentId,
|
||||||
|
model,
|
||||||
|
});
|
||||||
|
return { message: 'Live blog webhook processed successfully' };
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post()
|
||||||
|
async handleGenericWebhook(@Body() body: unknown) {
|
||||||
|
this.logger.log(`Received generic webhook: ${JSON.stringify(body)}`);
|
||||||
|
|
||||||
|
// Type guard to check if body is an object
|
||||||
|
if (typeof body !== 'object' || body === null) {
|
||||||
|
this.logger.warn(`Invalid webhook payload: ${JSON.stringify(body)}`);
|
||||||
|
return { message: 'Invalid payload' };
|
||||||
|
}
|
||||||
|
|
||||||
|
const bodyObj = body as Record<string, unknown>;
|
||||||
|
|
||||||
|
// Try to extract event and model from different possible payload structures
|
||||||
|
const event = (bodyObj.event || bodyObj.type) as string;
|
||||||
|
const model = (bodyObj.model || bodyObj.contentType) as string;
|
||||||
|
const entry = bodyObj.entry || bodyObj.data || bodyObj;
|
||||||
|
|
||||||
|
if (!event || !model) {
|
||||||
|
this.logger.warn(
|
||||||
|
`Cannot process webhook: missing event or model. Payload: ${JSON.stringify(body)}`,
|
||||||
|
);
|
||||||
|
return { message: 'Cannot process: missing event or model' };
|
||||||
|
}
|
||||||
|
|
||||||
|
const entryObj = entry as Record<string, unknown>;
|
||||||
|
const documentId = (entryObj.documentId ||
|
||||||
|
entryObj.id ||
|
||||||
|
(entryObj.document as Record<string, unknown>)?.id) as string;
|
||||||
|
|
||||||
|
if (!documentId) {
|
||||||
|
this.logger.warn(
|
||||||
|
`Cannot process webhook: missing documentId. Payload: ${JSON.stringify(body)}`,
|
||||||
|
);
|
||||||
|
return { message: 'Cannot process: missing documentId' };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate event type
|
||||||
|
const validEvents = [
|
||||||
|
'entry.create',
|
||||||
|
'entry.update',
|
||||||
|
'entry.delete',
|
||||||
|
'entry.publish',
|
||||||
|
];
|
||||||
|
if (!validEvents.includes(event)) {
|
||||||
|
this.logger.warn(`Invalid event type: ${event}`);
|
||||||
|
return { message: 'Invalid event type' };
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.strapiService.handleWebhook(
|
||||||
|
event as
|
||||||
|
| 'entry.create'
|
||||||
|
| 'entry.update'
|
||||||
|
| 'entry.delete'
|
||||||
|
| 'entry.publish',
|
||||||
|
{ documentId, model },
|
||||||
|
);
|
||||||
return { message: 'Webhook processed successfully' };
|
return { message: 'Webhook processed successfully' };
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -31,18 +114,6 @@ export class StrapiController {
|
|||||||
return { message: 'Articles sync completed' };
|
return { message: 'Articles sync completed' };
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post('live-blog')
|
|
||||||
async handleLiveBlogWebhook(@Body() body: WebhookBody) {
|
|
||||||
const { event, model, entry } = body;
|
|
||||||
|
|
||||||
if (model !== 'live-blog') {
|
|
||||||
return { message: 'Ignored: not a live blog' };
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.strapiService.handleWebhook(event, entry);
|
|
||||||
return { message: 'Live blog webhook processed successfully' };
|
|
||||||
}
|
|
||||||
|
|
||||||
@Post('sync/live-blogs')
|
@Post('sync/live-blogs')
|
||||||
async syncAllLiveBlogs() {
|
async syncAllLiveBlogs() {
|
||||||
await this.strapiService.syncLiveBlogs();
|
await this.strapiService.syncLiveBlogs();
|
||||||
|
|||||||
@ -245,7 +245,7 @@ export class StrapiService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async handleWebhook(
|
async handleWebhook(
|
||||||
event: 'entry.create' | 'entry.update' | 'entry.delete',
|
event: 'entry.create' | 'entry.update' | 'entry.delete' | 'entry.publish',
|
||||||
data: { documentId: string; model?: string },
|
data: { documentId: string; model?: string },
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
this.logger.log(
|
this.logger.log(
|
||||||
|
|||||||
@ -29,6 +29,16 @@
|
|||||||
},
|
},
|
||||||
"author": {
|
"author": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
|
},
|
||||||
|
"img": {
|
||||||
|
"type": "media",
|
||||||
|
"multiple": false,
|
||||||
|
"allowedTypes": [
|
||||||
|
"images",
|
||||||
|
"files",
|
||||||
|
"videos",
|
||||||
|
"audios"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
1
cms/cms/types/generated/contentTypes.d.ts
vendored
1
cms/cms/types/generated/contentTypes.d.ts
vendored
@ -446,6 +446,7 @@ export interface ApiArticleArticle extends Struct.CollectionTypeSchema {
|
|||||||
createdAt: Schema.Attribute.DateTime;
|
createdAt: Schema.Attribute.DateTime;
|
||||||
createdBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &
|
createdBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> &
|
||||||
Schema.Attribute.Private;
|
Schema.Attribute.Private;
|
||||||
|
img: Schema.Attribute.Media<'images' | 'files' | 'videos' | 'audios'>;
|
||||||
locale: Schema.Attribute.String & Schema.Attribute.Private;
|
locale: Schema.Attribute.String & Schema.Attribute.Private;
|
||||||
localizations: Schema.Attribute.Relation<
|
localizations: Schema.Attribute.Relation<
|
||||||
'oneToMany',
|
'oneToMany',
|
||||||
|
|||||||
1
whliveblog.md
Normal file
1
whliveblog.md
Normal file
@ -0,0 +1 @@
|
|||||||
|
Live-blog webhooks work correctly: POST /api/v1/webhooks/strapi/live-blog
|
||||||
Loading…
Reference in New Issue
Block a user