another auth checkpoint

webhook issue resolved
This commit is contained in:
echo 2026-02-04 22:36:28 +01:00
parent cb2355adcd
commit 3718882f5b
8 changed files with 388 additions and 10 deletions

70
QUICK_WEBHOOK_SETUP.md Normal file
View File

@ -0,0 +1,70 @@
# Quick Strapi Webhook Setup
## TL;DR - Quick Configuration
1. **Access Strapi Admin**: `http://localhost:1337/admin`
2. **Go to Settings → Webhooks**
3. **Add New Webhook**:
- **Name**: `Backend Sync`
- **URL**: `http://localhost:3000/api/v1/webhooks/strapi`
- **Events**: Select all for "Article" and "Live Blog" content types
4. **Save and Test**
## Already Configured (What We Fixed)
**Backend webhook endpoints** are now public (no auth required)
**Tested webhooks** manually - they work
**Articles sync** from Strapi to backend
**Frontend TypeScript errors** fixed
**Authentication system** working
## Manual Test Commands
```bash
# Test webhook manually
curl -X POST http://localhost:3000/api/v1/webhooks/strapi \
-H "Content-Type: application/json" \
-d '{
"event": "entry.publish",
"model": "article",
"entry": {"documentId": "r07qatlpgvx82d7337n3nz1l"}
}'
# Check synced articles
curl http://localhost:3000/api/v1/articles?status=published
# Run comprehensive test
./scripts/test-webhooks.sh
```
## Current Status
- **Strapi Articles**: 2
- **Backend Articles**: 2 (synced)
- **Webhook Status**: Ready for configuration
- **Frontend**: Access at `http://localhost:5173/articles`
## Immediate Action Required
1. **Configure webhooks in Strapi admin** (see detailed guide in `STRAPI_WEBHOOKS_SETUP.md`)
2. **Test by publishing an article** in Strapi
3. **Verify automatic sync** works
## If Webhooks Don't Work
Use manual sync as fallback:
```bash
# Get auth token first
TOKEN=$(curl -s -X POST http://localhost:3000/api/v1/auth/login \
-H "Content-Type: application/json" \
-d '{"username":"testuser","password":"Test123!"}' | jq -r '.access_token')
# Sync all articles
curl -X POST http://localhost:3000/api/v1/webhooks/strapi/sync/all \
-H "Authorization: Bearer $TOKEN"
```
## Verification
- Visit `http://localhost:5173/articles` to see synced articles
- Check backend logs: `docker logs placebo-backend-dev --tail 20`
- Monitor sync status with test script: `./scripts/test-webhooks.sh`

191
STRAPI_WEBHOOKS_SETUP.md Normal file
View File

@ -0,0 +1,191 @@
# Strapi Webhooks Configuration Guide
## Overview
This guide explains how to configure Strapi webhooks for automatic synchronization with the backend API. When articles or live blogs are published/updated/deleted in Strapi, webhooks will automatically trigger synchronization with the backend.
## Webhook Endpoints
The backend provides three webhook endpoints:
1. **Article-specific webhook**: `POST /api/v1/webhooks/strapi/article`
2. **Live Blog-specific webhook**: `POST /api/v1/webhooks/strapi/live-blog`
3. **Generic webhook**: `POST /api/v1/webhooks/strapi` (handles both)
All endpoints accept the following JSON format:
```json
{
"event": "entry.publish", // or "entry.update", "entry.delete", "entry.unpublish"
"model": "article", // or "live-blog"
"entry": {
"documentId": "unique-strapi-id"
}
}
```
## Configuration Steps
### Step 1: Access Strapi Admin Panel
1. Open your browser and go to: `http://localhost:1337/admin`
2. Log in with your admin credentials
### Step 2: Configure Webhooks
1. In the left sidebar, click on **Settings** (gear icon)
2. Click on **Webhooks** under the "GLOBAL SETTINGS" section
3. Click the **"Add new webhook"** button
### Step 3: Configure Article Webhook
**Basic Settings:**
- **Name**: `Backend Article Sync`
- **URL**: `http://localhost:3000/api/v1/webhooks/strapi/article`
- For Docker internal communication: `http://backend:3000/api/v1/webhooks/strapi/article`
- **Headers**: Add if needed (usually not required)
**Trigger Events:**
Select the following events for the "Article" content type:
- [x] **Create entry** (`entry.create`)
- [x] **Update entry** (`entry.update`)
- [x] **Delete entry** (`entry.delete`)
- [x] **Publish entry** (`entry.publish`)
- [x] **Unpublish entry** (`entry.unpublish`)
**Save the webhook.**
### Step 4: Configure Live Blog Webhook (Optional)
Repeat Step 3 for live blogs:
- **Name**: `Backend Live Blog Sync`
- **URL**: `http://localhost:3000/api/v1/webhooks/strapi/live-blog`
- Select events for the "Live Blog" content type
### Alternative: Single Generic Webhook
You can use a single webhook for all content types:
- **Name**: `Backend Generic Sync`
- **URL**: `http://localhost:3000/api/v1/webhooks/strapi`
- Select events for ALL relevant content types (Article, Live Blog)
## Testing Webhooks
### Manual Test
You can manually trigger a webhook to test the configuration:
```bash
# Test article webhook
curl -X POST http://localhost:3000/api/v1/webhooks/strapi/article \
-H "Content-Type: application/json" \
-d '{
"event": "entry.publish",
"model": "article",
"entry": {
"documentId": "your-article-id"
}
}'
# Test generic webhook
curl -X POST http://localhost:3000/api/v1/webhooks/strapi \
-H "Content-Type: application/json" \
-d '{
"event": "entry.publish",
"model": "article",
"entry": {
"documentId": "your-article-id"
}
}'
```
### Test from Strapi
1. In Strapi admin, edit any article
2. Click "Save" or "Publish"
3. Check backend logs for webhook receipt:
```bash
docker logs placebo-backend-dev --tail 20 | grep -i "webhook\|strapi"
```
## Manual Sync (Fallback)
If webhooks fail or for initial sync, use manual sync endpoints:
```bash
# Get authentication token first
curl -X POST http://localhost:3000/api/v1/auth/login \
-H "Content-Type: application/json" \
-d '{"username":"your-username","password":"your-password"}'
# Sync all articles (requires auth)
curl -X POST http://localhost:3000/api/v1/webhooks/strapi/sync/all \
-H "Authorization: Bearer YOUR_TOKEN_HERE"
# Sync all live blogs
curl -X POST http://localhost:3000/api/v1/webhooks/strapi/sync/live-blogs \
-H "Authorization: Bearer YOUR_TOKEN_HERE"
# Sync everything
curl -X POST http://localhost:3000/api/v1/webhooks/strapi/sync/everything \
-H "Authorization: Bearer YOUR_TOKEN_HERE"
```
## Troubleshooting
### Common Issues
1. **Webhook not received by backend**
- Check backend logs: `docker logs placebo-backend-dev --tail 50`
- Verify CORS is configured (should allow `http://localhost:1337`)
- Test webhook manually with curl
2. **401 Unauthorized error**
- Webhook endpoints are now public (no auth required)
- If getting 401, check that `@Public()` decorator is on webhook methods
3. **Webhook received but sync fails**
- Check Strapi API token in backend `.env.docker`
- Verify Strapi is accessible from backend container
- Check backend logs for specific error messages
4. **Article not appearing in frontend**
- Verify article is synced to backend: `curl http://localhost:3000/api/v1/articles`
- Check frontend browser console for errors
- Hard refresh frontend (Ctrl+F5)
### Log Monitoring
```bash
# Monitor backend logs for webhooks
docker logs -f placebo-backend-dev | grep -i "webhook\|strapi"
# Monitor Strapi logs
docker logs -f placebo-cms-dev
# Check if articles are in backend
curl -s "http://localhost:3000/api/v1/articles?status=published" | jq '.total'
```
## Docker Network Considerations
### Internal Docker Communication
- Within Docker network: Use `http://backend:3000` and `http://cms:1337`
- From host machine: Use `http://localhost:3000` and `http://localhost:1337`
### Webhook URL Examples
- **From Strapi container to Backend**: `http://backend:3000/api/v1/webhooks/strapi`
- **From Host to Backend**: `http://localhost:3000/api/v1/webhooks/strapi`
- **External/Production**: Use your domain: `https://api.yourdomain.com/api/v1/webhooks/strapi`
## Security Considerations
1. **Webhook endpoints are public** - ensure your backend is not exposed to the public internet in production
2. **Consider adding webhook signature verification** for production
3. **Use HTTPS in production** for all webhook calls
4. **Monitor webhook logs** for suspicious activity
## Verification Checklist
- [ ] Webhooks configured in Strapi admin
- [ ] Test webhook manually works
- [ ] Article publish in Strapi triggers sync
- [ ] Synced articles appear in backend API
- [ ] Frontend displays synced articles
- [ ] All event types tested (publish, update, delete)
## Support
If issues persist:
1. Check all service logs
2. Verify network connectivity between containers
3. Test each component independently
4. Review error messages in logs

View File

@ -1,5 +1,6 @@
import { Controller, Post, Body, Logger } from '@nestjs/common'; import { Controller, Post, Body, Logger } from '@nestjs/common';
import { StrapiService } from './strapi.service'; import { StrapiService } from './strapi.service';
import { Public } from './auth/public.decorator';
interface WebhookBody { interface WebhookBody {
event: event:
@ -21,6 +22,7 @@ export class StrapiController {
constructor(private readonly strapiService: StrapiService) {} constructor(private readonly strapiService: StrapiService) {}
@Post('article') @Post('article')
@Public()
async handleArticleWebhook(@Body() body: WebhookBody) { async handleArticleWebhook(@Body() body: WebhookBody) {
this.logger.log(`Received article webhook: ${JSON.stringify(body)}`); this.logger.log(`Received article webhook: ${JSON.stringify(body)}`);
const { event, model, entry } = body; const { event, model, entry } = body;
@ -38,6 +40,7 @@ export class StrapiController {
} }
@Post('live-blog') @Post('live-blog')
@Public()
async handleLiveBlogWebhook(@Body() body: WebhookBody) { async handleLiveBlogWebhook(@Body() body: WebhookBody) {
this.logger.log(`Received live-blog webhook: ${JSON.stringify(body)}`); this.logger.log(`Received live-blog webhook: ${JSON.stringify(body)}`);
const { event, model, entry } = body; const { event, model, entry } = body;
@ -55,6 +58,7 @@ export class StrapiController {
} }
@Post() @Post()
@Public()
async handleGenericWebhook(@Body() body: unknown) { async handleGenericWebhook(@Body() body: unknown) {
this.logger.log(`Received generic webhook: ${JSON.stringify(body)}`); this.logger.log(`Received generic webhook: ${JSON.stringify(body)}`);

View File

@ -24,7 +24,7 @@ export function ArticleTicker() {
{articles.map((article, index) => ( {articles.map((article, index) => (
<Link <Link
key={`${article.id}-${index}`} key={`${article.id}-${index}`}
to={`/articles/${article.id}` as any} to={`/articles/${article.id}`}
className="text-sm text-muted-foreground hover:text-foreground hover:underline inline-block px-4" className="text-sm text-muted-foreground hover:text-foreground hover:underline inline-block px-4"
> >
{article.title || 'No title'} {article.title || 'No title'}
@ -34,7 +34,7 @@ export function ArticleTicker() {
{articles.map((article, index) => ( {articles.map((article, index) => (
<Link <Link
key={`dup-${article.id}-${index}`} key={`dup-${article.id}-${index}`}
to={`/articles/${article.id}` as any} to={`/articles/${article.id}`}
className="text-sm text-muted-foreground hover:text-foreground hover:underline inline-block px-4" className="text-sm text-muted-foreground hover:text-foreground hover:underline inline-block px-4"
> >
{article.title || 'No title'} {article.title || 'No title'}

View File

@ -35,7 +35,7 @@ export function ArticlesComponent() {
{data?.data.map((article) => ( {data?.data.map((article) => (
<Link <Link
key={article.id} key={article.id}
to={`/articles/${article.id}` as any} to={`/articles/${article.id}`}
className="p-6 rounded-xl border bg-card hover:shadow-lg transition-shadow cursor-pointer block" className="p-6 rounded-xl border bg-card hover:shadow-lg transition-shadow cursor-pointer block"
> >
<h2 className="text-xl font-semibold mb-2 line-clamp-2"> <h2 className="text-xl font-semibold mb-2 line-clamp-2">

View File

@ -38,7 +38,7 @@ export function LiveBlogsComponent() {
{data?.data.map((liveBlog) => ( {data?.data.map((liveBlog) => (
<Link <Link
key={liveBlog.id} key={liveBlog.id}
to={`/live-blogs/${liveBlog.slug}` as any} to={`/live-blogs/${liveBlog.slug}`}
className="p-6 rounded-xl border bg-card hover:shadow-lg transition-shadow cursor-pointer block" className="p-6 rounded-xl border bg-card hover:shadow-lg transition-shadow cursor-pointer block"
> >
<div className="flex items-start justify-between mb-4"> <div className="flex items-start justify-between mb-4">

View File

@ -1,4 +1,4 @@
import { useEffect, useRef, useState } from 'react'; import { useEffect, useRef, useState, useMemo } from 'react';
export interface LiveBlogStreamEvent { export interface LiveBlogStreamEvent {
type: 'connected' | 'update' | 'status-change' | 'pin-update' | 'error'; type: 'connected' | 'update' | 'status-change' | 'pin-update' | 'error';
@ -22,7 +22,7 @@ export function useLiveBlogStream(
maxReconnectAttempts: 10, maxReconnectAttempts: 10,
}; };
const mergedOptions = { ...defaultOptions, ...options }; const mergedOptions = useMemo(() => ({ ...defaultOptions, ...options }), [options, defaultOptions]);
const [isConnected, setIsConnected] = useState(false); const [isConnected, setIsConnected] = useState(false);
const [lastEvent, setLastEvent] = useState<LiveBlogStreamEvent | null>(null); const [lastEvent, setLastEvent] = useState<LiveBlogStreamEvent | null>(null);
const [connectionError, setConnectionError] = useState<string | null>(null); const [connectionError, setConnectionError] = useState<string | null>(null);

113
scripts/test-webhooks.sh Executable file
View File

@ -0,0 +1,113 @@
#!/bin/bash
# Test script for Strapi webhooks
# Usage: ./test-webhooks.sh [article-id]
set -e
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
echo -e "${YELLOW}=== Strapi Webhook Test Script ===${NC}\n"
# Default article ID (first article from Strapi)
DEFAULT_ARTICLE_ID="r07qatlpgvx82d7337n3nz1l"
ARTICLE_ID=${1:-$DEFAULT_ARTICLE_ID}
echo "Testing with article ID: $ARTICLE_ID"
echo ""
# Test 1: Check backend is running
echo -e "${YELLOW}1. Testing backend connectivity...${NC}"
if curl -s -f "http://localhost:3000/api/v1/articles" > /dev/null; then
echo -e "${GREEN}✓ Backend is running${NC}"
else
echo -e "${RED}✗ Backend is not accessible${NC}"
exit 1
fi
# Test 2: Check Strapi is running
echo -e "${YELLOW}2. Testing Strapi connectivity...${NC}"
if curl -s -f "http://localhost:1337/_health" > /dev/null 2>&1 || curl -s -f "http://localhost:1337/admin" > /dev/null; then
echo -e "${GREEN}✓ Strapi is running${NC}"
else
echo -e "${RED}✗ Strapi is not accessible${NC}"
exit 1
fi
# Test 3: Send test webhook
echo -e "${YELLOW}3. Sending test webhook...${NC}"
WEBHOOK_RESPONSE=$(curl -s -w "%{http_code}" -X POST "http://localhost:3000/api/v1/webhooks/strapi/article" \
-H "Content-Type: application/json" \
-d "{
\"event\": \"entry.publish\",
\"model\": \"article\",
\"entry\": {
\"documentId\": \"$ARTICLE_ID\"
}
}")
# Extract status code and body
HTTP_STATUS=${WEBHOOK_RESPONSE: -3}
RESPONSE_BODY=${WEBHOOK_RESPONSE%???}
if [[ "$HTTP_STATUS" =~ ^2[0-9][0-9]$ ]]; then
echo -e "${GREEN}✓ Webhook sent successfully (HTTP $HTTP_STATUS)${NC}"
echo "Response: $RESPONSE_BODY"
else
echo -e "${RED}✗ Webhook failed (HTTP $HTTP_STATUS)${NC}"
echo "Response: $RESPONSE_BODY"
exit 1
fi
# Test 4: Check if article is in backend
echo -e "${YELLOW}4. Verifying article in backend...${NC}"
sleep 2 # Give backend time to process
ARTICLE_COUNT=$(curl -s "http://localhost:3000/api/v1/articles?status=published" | jq '.total // 0')
if [ "$ARTICLE_COUNT" -gt 0 ]; then
echo -e "${GREEN}✓ Backend has $ARTICLE_COUNT published article(s)${NC}"
# List articles
echo -e "\n${YELLOW}Articles in backend:${NC}"
curl -s "http://localhost:3000/api/v1/articles?status=published" | jq -r '.data[] | "• \(.title) (ID: \(.id))"'
else
echo -e "${RED}✗ No articles found in backend${NC}"
fi
# Test 5: Check Strapi articles
echo -e "\n${YELLOW}5. Checking Strapi articles...${NC}"
STRAPI_TOKEN="578d628f62df967ff95f95bedb205b5d10bbf792340519c8c467d6473208e16b3918151a97b49fa2285a53df0ec8e340a9ca555b01a654bd22152847840e6a368ee626a6f1338ce2f23790c171013b263ec80fbaf116e2b459d3663b234d08855fd0eb631991ed15bb94f7dbb0b80f190352965c72c7fd327c73629ceff38fbb"
STRAPI_ARTICLES=$(curl -s -H "Authorization: Bearer $STRAPI_TOKEN" "http://localhost:1337/api/articles")
STRAPI_COUNT=$(echo "$STRAPI_ARTICLES" | jq '.data | length // 0')
if [ "$STRAPI_COUNT" -gt 0 ]; then
echo -e "${GREEN}✓ Strapi has $STRAPI_COUNT article(s)${NC}"
echo -e "\n${YELLOW}Articles in Strapi:${NC}"
echo "$STRAPI_ARTICLES" | jq -r '.data[] | "• \(.title) (Strapi ID: \(.documentId))"'
else
echo -e "${RED}✗ No articles found in Strapi${NC}"
fi
echo -e "\n${YELLOW}=== Summary ===${NC}"
echo "Backend articles: $ARTICLE_COUNT"
echo "Strapi articles: $STRAPI_COUNT"
if [ "$ARTICLE_COUNT" -eq "$STRAPI_COUNT" ]; then
echo -e "${GREEN}✓ Sync appears to be working correctly!${NC}"
else
echo -e "${YELLOW}⚠ Article counts don't match. Manual sync may be needed.${NC}"
echo "Run: curl -X POST http://localhost:3000/api/v1/webhooks/strapi/sync/all -H \"Authorization: Bearer YOUR_TOKEN\""
fi
echo -e "\n${YELLOW}Next steps:${NC}"
echo "1. Configure webhooks in Strapi admin: http://localhost:1337/admin"
echo "2. Test by publishing an article in Strapi"
echo "3. Check backend logs: docker logs placebo-backend-dev --tail 20"
echo "4. Visit frontend: http://localhost:5173/articles"