Initial commit
This commit is contained in:
396
skills/plugin-backend-dev/SKILL.md
Normal file
396
skills/plugin-backend-dev/SKILL.md
Normal file
@@ -0,0 +1,396 @@
|
||||
---
|
||||
name: plugin-backend-dev
|
||||
description: Create and manage backend servers for Obsidian plugins that need server-side processing
|
||||
---
|
||||
|
||||
You are an expert in creating backend servers for Obsidian plugins.
|
||||
|
||||
# When Backends Are Needed
|
||||
- Python libraries (ML, embeddings, NLP)
|
||||
- Heavy computation
|
||||
- Database operations not possible in browser
|
||||
- Node packages that don't work in browser context
|
||||
- Long-running processes
|
||||
|
||||
# Your Tools
|
||||
- Write: Create server files
|
||||
- Edit: Update server code
|
||||
- Bash: Test server functionality
|
||||
- Read: Check existing implementations
|
||||
|
||||
# Backend Structure
|
||||
|
||||
## Directory Layout
|
||||
```
|
||||
plugin-root/
|
||||
├── plugin/ # Obsidian plugin
|
||||
│ ├── main.ts
|
||||
│ └── ...
|
||||
└── server/ # Backend server
|
||||
├── src/
|
||||
│ ├── server.ts # Server entry point
|
||||
│ ├── routes/
|
||||
│ └── services/
|
||||
├── package.json
|
||||
├── tsconfig.json
|
||||
└── .env
|
||||
```
|
||||
|
||||
## Express + TypeScript Server Template
|
||||
|
||||
### server/package.json
|
||||
```json
|
||||
{
|
||||
"name": "plugin-server",
|
||||
"version": "1.0.0",
|
||||
"main": "dist/server.js",
|
||||
"scripts": {
|
||||
"dev": "ts-node-dev --respawn src/server.ts",
|
||||
"build": "tsc",
|
||||
"start": "node dist/server.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"express": "^4.18.2",
|
||||
"cors": "^2.8.5",
|
||||
"dotenv": "^16.0.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/express": "^4.17.17",
|
||||
"@types/cors": "^2.8.13",
|
||||
"@types/node": "^18.15.0",
|
||||
"ts-node-dev": "^2.0.0",
|
||||
"typescript": "^5.0.0"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### server/src/server.ts
|
||||
```typescript
|
||||
import express, { Request, Response } from 'express';
|
||||
import cors from 'cors';
|
||||
import dotenv from 'dotenv';
|
||||
|
||||
dotenv.config();
|
||||
|
||||
const app = express();
|
||||
const PORT = process.env.PORT || 3000;
|
||||
|
||||
// Middleware
|
||||
app.use(cors());
|
||||
app.use(express.json());
|
||||
|
||||
// Health check
|
||||
app.get('/health', (req: Request, res: Response) => {
|
||||
res.json({ status: 'ok', timestamp: new Date().toISOString() });
|
||||
});
|
||||
|
||||
// Example API endpoint
|
||||
app.post('/api/process', async (req: Request, res: Response) => {
|
||||
try {
|
||||
const { data } = req.body;
|
||||
const result = await processData(data);
|
||||
res.json({ success: true, result });
|
||||
} catch (error) {
|
||||
console.error('Error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
async function processData(data: any): Promise<any> {
|
||||
// Process data here
|
||||
return data;
|
||||
}
|
||||
|
||||
app.listen(PORT, () => {
|
||||
console.log(`Server running on http://localhost:${PORT}`);
|
||||
});
|
||||
```
|
||||
|
||||
### server/tsconfig.json
|
||||
```json
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"module": "commonjs",
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./src",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
```
|
||||
|
||||
## Fastify Alternative (Faster)
|
||||
```typescript
|
||||
import Fastify from 'fastify';
|
||||
import cors from '@fastify/cors';
|
||||
|
||||
const fastify = Fastify({ logger: true });
|
||||
|
||||
fastify.register(cors);
|
||||
|
||||
fastify.get('/health', async (request, reply) => {
|
||||
return { status: 'ok' };
|
||||
});
|
||||
|
||||
fastify.post('/api/process', async (request, reply) => {
|
||||
const { data } = request.body as any;
|
||||
const result = await processData(data);
|
||||
return { success: true, result };
|
||||
});
|
||||
|
||||
const start = async () => {
|
||||
try {
|
||||
await fastify.listen({ port: 3000 });
|
||||
} catch (err) {
|
||||
fastify.log.error(err);
|
||||
process.exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
start();
|
||||
```
|
||||
|
||||
## Python Backend with Flask
|
||||
|
||||
### server/requirements.txt
|
||||
```
|
||||
Flask==2.3.0
|
||||
Flask-CORS==4.0.0
|
||||
python-dotenv==1.0.0
|
||||
```
|
||||
|
||||
### server/server.py
|
||||
```python
|
||||
from flask import Flask, request, jsonify
|
||||
from flask_cors import CORS
|
||||
import os
|
||||
from dotenv import load_dotenv
|
||||
|
||||
load_dotenv()
|
||||
|
||||
app = Flask(__name__)
|
||||
CORS(app)
|
||||
|
||||
@app.route('/health', methods=['GET'])
|
||||
def health():
|
||||
return jsonify({'status': 'ok'})
|
||||
|
||||
@app.route('/api/process', methods=['POST'])
|
||||
def process():
|
||||
try:
|
||||
data = request.json.get('data')
|
||||
result = process_data(data)
|
||||
return jsonify({'success': True, 'result': result})
|
||||
except Exception as e:
|
||||
return jsonify({'success': False, 'error': str(e)}), 500
|
||||
|
||||
def process_data(data):
|
||||
# Process data here
|
||||
return data
|
||||
|
||||
if __name__ == '__main__':
|
||||
port = int(os.getenv('PORT', 3000))
|
||||
app.run(host='0.0.0.0', port=port, debug=True)
|
||||
```
|
||||
|
||||
## Plugin-Side Integration
|
||||
|
||||
### services/BackendService.ts
|
||||
```typescript
|
||||
export class BackendService {
|
||||
private baseUrl: string;
|
||||
private isHealthy: boolean = false;
|
||||
|
||||
constructor(baseUrl: string = 'http://localhost:3000') {
|
||||
this.baseUrl = baseUrl;
|
||||
this.startHealthCheck();
|
||||
}
|
||||
|
||||
async checkHealth(): Promise<boolean> {
|
||||
try {
|
||||
const response = await fetch(`${this.baseUrl}/health`, {
|
||||
method: 'GET',
|
||||
signal: AbortSignal.timeout(5000)
|
||||
});
|
||||
this.isHealthy = response.ok;
|
||||
return this.isHealthy;
|
||||
} catch {
|
||||
this.isHealthy = false;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async processData(data: any): Promise<any> {
|
||||
if (!this.isHealthy) {
|
||||
throw new Error('Backend server is not available');
|
||||
}
|
||||
|
||||
const response = await fetch(`${this.baseUrl}/api/process`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ data })
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Server error: ${response.statusText}`);
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
if (!result.success) {
|
||||
throw new Error(result.error || 'Unknown error');
|
||||
}
|
||||
|
||||
return result.result;
|
||||
}
|
||||
|
||||
private startHealthCheck(): void {
|
||||
// Initial check
|
||||
this.checkHealth();
|
||||
|
||||
// Periodic checks
|
||||
setInterval(() => this.checkHealth(), 30000);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### 1. File Processing
|
||||
```typescript
|
||||
// Server endpoint
|
||||
app.post('/api/process-file', async (req, res) => {
|
||||
const { content, filename } = req.body;
|
||||
const result = await processFile(content, filename);
|
||||
res.json({ result });
|
||||
});
|
||||
|
||||
// Plugin side
|
||||
async processFile(file: TFile): Promise<any> {
|
||||
const content = await this.app.vault.read(file);
|
||||
return await this.backend.processData({
|
||||
content,
|
||||
filename: file.name
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Batch Processing
|
||||
```typescript
|
||||
// Server with queue
|
||||
import Queue from 'bull';
|
||||
const processingQueue = new Queue('processing');
|
||||
|
||||
processingQueue.process(async (job) => {
|
||||
return processData(job.data);
|
||||
});
|
||||
|
||||
app.post('/api/batch', async (req, res) => {
|
||||
const { items } = req.body;
|
||||
const jobs = await Promise.all(
|
||||
items.map(item => processingQueue.add(item))
|
||||
);
|
||||
res.json({ jobIds: jobs.map(j => j.id) });
|
||||
});
|
||||
```
|
||||
|
||||
### 3. Streaming Responses
|
||||
```typescript
|
||||
// Server with streaming
|
||||
app.get('/api/stream', (req, res) => {
|
||||
res.setHeader('Content-Type', 'text/event-stream');
|
||||
res.setHeader('Cache-Control', 'no-cache');
|
||||
res.setHeader('Connection', 'keep-alive');
|
||||
|
||||
const interval = setInterval(() => {
|
||||
res.write(`data: ${JSON.stringify({ time: Date.now() })}\n\n`);
|
||||
}, 1000);
|
||||
|
||||
req.on('close', () => {
|
||||
clearInterval(interval);
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
## Development Workflow
|
||||
|
||||
1. Start server in dev mode:
|
||||
```bash
|
||||
cd server
|
||||
npm run dev
|
||||
```
|
||||
|
||||
2. Test endpoints:
|
||||
```bash
|
||||
curl http://localhost:3000/health
|
||||
curl -X POST http://localhost:3000/api/process \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"data": "test"}'
|
||||
```
|
||||
|
||||
3. Build for production:
|
||||
```bash
|
||||
npm run build
|
||||
npm start
|
||||
```
|
||||
|
||||
## Docker Deployment (Optional)
|
||||
|
||||
### Dockerfile
|
||||
```dockerfile
|
||||
FROM node:18-alpine
|
||||
WORKDIR /app
|
||||
COPY package*.json ./
|
||||
RUN npm ci --production
|
||||
COPY dist ./dist
|
||||
EXPOSE 3000
|
||||
CMD ["node", "dist/server.js"]
|
||||
```
|
||||
|
||||
### docker-compose.yml
|
||||
```yaml
|
||||
version: '3.8'
|
||||
services:
|
||||
server:
|
||||
build: ./server
|
||||
ports:
|
||||
- "3000:3000"
|
||||
environment:
|
||||
- PORT=3000
|
||||
- NODE_ENV=production
|
||||
restart: unless-stopped
|
||||
```
|
||||
|
||||
# Best Practices
|
||||
1. Always include health check endpoint
|
||||
2. Use proper error handling
|
||||
3. Add request timeout handling
|
||||
4. Validate input data
|
||||
5. Use environment variables for config
|
||||
6. Add logging for debugging
|
||||
7. Consider rate limiting for production
|
||||
8. Use CORS appropriately
|
||||
|
||||
# Security Considerations
|
||||
1. Validate all input
|
||||
2. Use HTTPS in production
|
||||
3. Implement authentication if needed
|
||||
4. Sanitize file paths
|
||||
5. Limit request sizes
|
||||
6. Add rate limiting
|
||||
|
||||
When creating a backend:
|
||||
1. Ask what processing is needed
|
||||
2. Choose appropriate tech stack
|
||||
3. Create server structure
|
||||
4. Implement endpoints
|
||||
5. Create plugin-side service
|
||||
6. Test integration
|
||||
7. Provide deployment instructions
|
||||
Reference in New Issue
Block a user