Skip to content

Commit c537d76

Browse files
authored
Merge pull request #46 from techdiary-dev/shoaibsharif/delete-scheduled-articles
feat: schedule article deletion
2 parents 8b9eb6f + 182f587 commit c537d76

File tree

10 files changed

+6210
-2
lines changed

10 files changed

+6210
-2
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ yarn-error.log*
3232

3333
# local env files
3434
.env*.local
35+
.env
36+
.dev.vars
3537

3638
# vercel
3739
.vercel

CLAUDE.md

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
# CLAUDE.md
2+
3+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4+
5+
## Development Commands
6+
7+
### Development Server
8+
- `bun run dev` - Start development server with Turbo
9+
- Open http://localhost:3000 to view the application
10+
11+
### Database Operations
12+
- `bun run db:generate` - Generate database migrations from schema changes
13+
- `bun run db:push` - Push schema changes to database
14+
- `bun run db:studio` - Open Drizzle Studio (database GUI)
15+
16+
### Build & Deployment
17+
- `bun run build` - Build for production
18+
- `bun run start` - Start production server
19+
- `bun run lint` - Run ESLint checks
20+
21+
### Backend Development
22+
- `bun run play` - Run backend playground script (`src/backend/play.ts`)
23+
24+
## Architecture Overview
25+
26+
### Technology Stack
27+
- **Frontend**: Next.js 15 (App Router), React 19, TypeScript
28+
- **Styling**: Tailwind CSS, shadcn/ui components
29+
- **Backend**: Next.js API routes, Drizzle ORM
30+
- **Database**: PostgreSQL
31+
- **Authentication**: GitHub OAuth
32+
- **Search**: MeilSearch
33+
- **File Storage**: Cloudinary
34+
- **State Management**: Jotai, React Hook Form with Zod validation
35+
36+
### Core Directory Structure
37+
38+
#### Frontend (`/src/app/`)
39+
- Route groups using Next.js App Router:
40+
- `(home)` - Main homepage and article feed
41+
- `(dashboard-editor)` - Protected dashboard routes
42+
- `[username]` - User profile pages
43+
- `[username]/[articleHandle]` - Individual article pages
44+
- API routes in `/api/` for OAuth and development
45+
46+
#### Backend (`/src/backend/`)
47+
- **Domain Models** (`/domain/`) - Core business logic entities
48+
- **Persistence** (`/persistence/`) - Database schemas and repositories
49+
- **Services** (`/services/`) - Business logic actions
50+
- **Input Validation** - Zod schemas for type-safe inputs
51+
52+
#### Component Architecture (`/src/components/`)
53+
- **UI Components** - shadcn/ui based design system
54+
- **Feature Components** - Domain-specific (Editor, Navbar, etc.)
55+
- **Layout Components** - Page layouts and providers
56+
57+
### Database Schema Architecture
58+
59+
Key entities and their relationships:
60+
- **Users** - User profiles with social authentication
61+
- **Articles** - Blog posts with markdown content and metadata
62+
- **Series** - Article collections/sequences
63+
- **Comments** - Nested commenting system with resource association
64+
- **Tags** - Article categorization
65+
- **Bookmarks** - User content saving
66+
- **Reactions** - Emoji-based reactions (LOVE, FIRE, WOW, etc.)
67+
- **User Sessions** - Session management
68+
- **User Socials** - OAuth provider connections
69+
70+
### Content Management
71+
- **Rich Text**: Markdoc for markdown parsing and rendering
72+
- **File Uploads**: Cloudinary integration for images/media
73+
- **Search**: MeilSearch for full-text search capabilities
74+
- **Internationalization**: Custom i18n implementation (Bengali/English)
75+
76+
### State Management Patterns
77+
- **Server State**: Drizzle ORM with PostgreSQL
78+
- **Client State**: Jotai for global state management
79+
- **Form State**: React Hook Form with Zod validation
80+
- **Environment**: Type-safe environment variables with @t3-oss/env-nextjs
81+
82+
## Required Environment Variables
83+
84+
Server-side:
85+
- `DATABASE_URL` - PostgreSQL connection string
86+
- `GITHUB_CLIENT_ID` - GitHub OAuth client ID
87+
- `GITHUB_CLIENT_SECRET` - GitHub OAuth client secret
88+
- `GITHUB_CALLBACK_URL` - OAuth callback URL
89+
- `CLOUDINARY_URL` - Cloudinary configuration
90+
- `MEILISEARCH_ADMIN_API_KEY` - MeilSearch admin API key
91+
92+
Client-side:
93+
- `NEXT_PUBLIC_MEILISEARCH_API_HOST` - MeilSearch API host URL
94+
- `NEXT_PUBLIC_MEILISEARCH_SEARCH_API_KEY` - MeilSearch search API key
95+
96+
## Key Features Implementation
97+
98+
### Authentication Flow
99+
- GitHub OAuth integration via `/api/auth/github`
100+
- User sessions managed in `userSessionsTable`
101+
- Social provider connections in `userSocialsTable`
102+
103+
### Content Creation
104+
- Rich markdown editor with drag-and-drop support
105+
- Image upload and optimization via Cloudinary
106+
- Article series management for content organization
107+
- Tag-based categorization system
108+
109+
### Search Implementation
110+
- MeilSearch for full-text search across articles
111+
- Search configuration and indexing handled in backend services
112+
- Client-side search interface with real-time results
113+
114+
### Community Features
115+
- Nested commenting system with resource association
116+
- Emoji-based reactions (LOVE, FIRE, WOW, etc.)
117+
- User following and bookmarking functionality
118+
- Social sharing and user profiles
119+
120+
## Development Workflow
121+
122+
1. Database changes require running `npm run db:generate` followed by `npm run db:push`
123+
2. Backend logic testing can be done via `npm run play` playground script
124+
3. Type safety is enforced through Zod schemas for all inputs
125+
4. UI components follow shadcn/ui patterns and conventions
126+
5. All forms use React Hook Form with Zod validation schemas
127+
128+
## Special Considerations
129+
130+
- **Bengali Language Support**: Custom font loading (Kohinoor Bangla) and i18n
131+
- **SEO Optimization**: Dynamic sitemaps, Open Graph tags, and schema markup
132+
- **Performance**: Next.js Image optimization, bundle splitting, and caching
133+
- **Security**: Input validation via Zod, secure OAuth flow, environment variable validation

CLOUDFLARE_CRON_SETUP.md

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
# Cloudflare Cron Setup for Article Cleanup
2+
3+
This document explains how to set up Cloudflare Cron Triggers to automatically delete expired articles.
4+
5+
## Overview
6+
7+
The system consists of:
8+
1. **API Route**: `/api/cron/cleanup-articles` - Handles the actual cleanup logic
9+
2. **Cloudflare Worker**: `src/workers/cron-worker.ts` - Cron trigger that calls the API
10+
3. **Backend Service**: `src/backend/services/article-cleanup-service.ts` - Database operations
11+
12+
## Setup Instructions
13+
14+
### 1. Install Wrangler CLI
15+
16+
```bash
17+
npm install -g wrangler
18+
# or
19+
bun install -g wrangler
20+
```
21+
22+
### 2. Authenticate with Cloudflare
23+
24+
```bash
25+
wrangler login
26+
```
27+
28+
### 3. Configure Environment Variables
29+
30+
Update `wrangler.toml` with your actual domain:
31+
32+
```toml
33+
[vars]
34+
CRON_TARGET_URL = "https://your-actual-domain.com/api/cron/cleanup-articles"
35+
```
36+
37+
### 4. Set Secret (Optional but Recommended)
38+
39+
For additional security, set a secret that the cron worker will send:
40+
41+
```bash
42+
wrangler secret put CRON_SECRET
43+
# Enter your secret when prompted
44+
```
45+
46+
Then add the same secret to your Next.js environment:
47+
48+
```bash
49+
# .env.local
50+
CRON_SECRET=your-secret-key
51+
```
52+
53+
### 5. Deploy the Cron Worker
54+
55+
```bash
56+
wrangler deploy src/workers/cron-worker.ts
57+
```
58+
59+
### 6. Verify Cron Schedule
60+
61+
The cron is configured to run daily at 2:00 AM UTC. You can modify the schedule in `wrangler.toml`:
62+
63+
```toml
64+
[triggers]
65+
crons = [
66+
"0 2 * * *" # Daily at 2:00 AM UTC
67+
]
68+
```
69+
70+
Other common schedules:
71+
- `"0 * * * *"` - Every hour
72+
- `"0 2 * * 0"` - Every Sunday at 2:00 AM
73+
- `"0 2 1 * *"` - First day of every month at 2:00 AM
74+
75+
### 7. Monitor Cron Executions
76+
77+
You can monitor executions in the Cloudflare dashboard:
78+
1. Go to Workers & Pages
79+
2. Select your worker
80+
3. Check the "Logs" tab for execution logs
81+
82+
## Manual Testing
83+
84+
You can test the cleanup manually by calling the API directly:
85+
86+
```bash
87+
# GET request for testing
88+
curl https://your-domain.com/api/cron/cleanup-articles
89+
90+
# POST request (mimics cron call)
91+
curl -X POST https://your-domain.com/api/cron/cleanup-articles \
92+
-H "Content-Type: application/json" \
93+
-H "x-cron-secret: your-secret-key"
94+
```
95+
96+
## How It Works
97+
98+
1. **Article Scheduling**: Articles can be scheduled for deletion by setting the `delete_scheduled_at` field
99+
2. **Cron Trigger**: Cloudflare runs the worker daily at 2:00 AM UTC
100+
3. **API Call**: Worker calls `/api/cron/cleanup-articles` endpoint
101+
4. **Cleanup Logic**: Service finds articles where `delete_scheduled_at < current_time` and deletes them
102+
5. **Response**: API returns count of deleted articles and their details
103+
104+
## Database Schema
105+
106+
Articles are scheduled for deletion using the `delete_scheduled_at` timestamp field:
107+
108+
```sql
109+
-- Example of scheduling an article for deletion in 30 days
110+
UPDATE articles
111+
SET delete_scheduled_at = NOW() + INTERVAL '30 days'
112+
WHERE id = 'article-id';
113+
```
114+
115+
## Additional Functions
116+
117+
The cleanup service also provides:
118+
- `scheduleArticleForDeletion(articleId, deleteAt)` - Schedule an article
119+
- `cancelScheduledDeletion(articleId)` - Cancel scheduled deletion
120+
- `getScheduledArticles()` - Get all articles scheduled for deletion
121+
122+
## Troubleshooting
123+
124+
### Common Issues
125+
126+
1. **404 Error**: Check that your `CRON_TARGET_URL` is correct
127+
2. **401 Unauthorized**: Verify `CRON_SECRET` matches between worker and API
128+
3. **500 Error**: Check database connection and permissions
129+
4. **No Articles Deleted**: Verify articles have `delete_scheduled_at` set and are past due
130+
131+
### Debugging
132+
133+
1. Check Cloudflare Worker logs in the dashboard
134+
2. Check your Next.js application logs
135+
3. Test the API endpoint manually
136+
4. Verify database records with `getScheduledArticles()`
137+
138+
## Security Considerations
139+
140+
1. Use `CRON_SECRET` to verify requests come from your worker
141+
2. Consider rate limiting the endpoint
142+
3. Log all cleanup operations for audit trails
143+
4. Ensure proper database permissions for the cleanup operations

package.json

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@
1010
"db:generate": "npx drizzle-kit generate",
1111
"db:push": "npx drizzle-kit push",
1212
"db:studio": "npx drizzle-kit studio",
13-
"play": "npx tsx ./src/backend/play.ts"
13+
"play": "npx tsx ./src/backend/play.ts",
14+
"wrangler:dev": "wrangler dev",
15+
"cf-typegen": "wrangler types"
1416
},
1517
"dependencies": {
1618
"@aws-sdk/client-s3": "^3.828.0",
@@ -80,6 +82,7 @@
8082
"prettier": "^3.5.3",
8183
"tailwindcss": "^4.1.1",
8284
"tsx": "^4.19.3",
83-
"typescript": "^5.8.2"
85+
"typescript": "^5.8.2",
86+
"wrangler": "^4.20.0"
8487
}
8588
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import { NextRequest, NextResponse } from "next/server";
2+
import { deleteExpiredArticles } from "@/backend/services/article-cleanup-service";
3+
4+
export async function POST(request: NextRequest) {
5+
try {
6+
// Verify the request is from Cloudflare Cron (optional security measure)
7+
const cronSecret = request.headers.get("x-cron-secret");
8+
if (process.env.CRON_SECRET && cronSecret !== process.env.CRON_SECRET) {
9+
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
10+
}
11+
12+
// Execute the cleanup
13+
const result = await deleteExpiredArticles();
14+
15+
return NextResponse.json({
16+
success: true,
17+
message: `Successfully deleted ${result.deletedCount} expired articles`,
18+
deletedCount: result.deletedCount,
19+
});
20+
} catch (error) {
21+
console.error("Error in cleanup-articles cron job:", error);
22+
return NextResponse.json(
23+
{
24+
error: "Internal server error",
25+
message: error instanceof Error ? error.message : "Unknown error",
26+
},
27+
{ status: 500 }
28+
);
29+
}
30+
}
31+
32+
// Also support GET for manual testing
33+
export async function GET() {
34+
try {
35+
const result = await deleteExpiredArticles();
36+
37+
return NextResponse.json({
38+
success: true,
39+
message: `Successfully deleted ${result.deletedCount} expired articles`,
40+
count: result.deletedCount,
41+
});
42+
} catch (error) {
43+
console.error("Error in cleanup-articles manual execution:", error);
44+
return NextResponse.json(
45+
{
46+
error: "Internal server error",
47+
message: error instanceof Error ? error.message : "Unknown error",
48+
},
49+
{ status: 500 }
50+
);
51+
}
52+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
"use server";
2+
import { and, lt, neq } from "sqlkit";
3+
import { persistenceRepository } from "../persistence/persistence-repositories";
4+
import { handleActionException } from "./RepositoryException";
5+
6+
/**
7+
* Delete articles that have passed their scheduled deletion time
8+
*/
9+
export async function deleteExpiredArticles() {
10+
try {
11+
const currentTime = new Date();
12+
13+
const deleteResult = await persistenceRepository.article.delete({
14+
where: and(
15+
neq("delete_scheduled_at", null),
16+
lt("delete_scheduled_at", currentTime)
17+
),
18+
});
19+
20+
console.log(
21+
`Successfully deleted ${deleteResult?.rowCount} expired articles`
22+
);
23+
24+
return {
25+
deletedCount: deleteResult?.rowCount || 0,
26+
};
27+
} catch (error) {
28+
console.error("Error deleting expired articles:", error);
29+
throw handleActionException(error);
30+
}
31+
}

0 commit comments

Comments
 (0)