Initial commit
This commit is contained in:
472
skills/backend-endpoint/SKILL.md
Normal file
472
skills/backend-endpoint/SKILL.md
Normal file
@@ -0,0 +1,472 @@
|
||||
---
|
||||
name: backend-endpoint
|
||||
description: Create REST/GraphQL API endpoint with validation, error handling, and tests. Auto-invoke when user says "add endpoint", "create API", "new route", or "add route".
|
||||
allowed-tools: Read, Write, Edit, Grep, Glob, Bash
|
||||
version: 1.0.0
|
||||
---
|
||||
|
||||
# Backend API Endpoint Generator
|
||||
|
||||
Generate production-ready REST or GraphQL endpoints with request validation, error handling, and comprehensive tests.
|
||||
|
||||
## When to Invoke
|
||||
|
||||
Auto-invoke when user mentions:
|
||||
- "Add endpoint"
|
||||
- "Create API"
|
||||
- "New route"
|
||||
- "Add route"
|
||||
- "Create API endpoint for [resource]"
|
||||
|
||||
## What This Does
|
||||
|
||||
1. Generates route handler with proper HTTP methods
|
||||
2. Adds request validation (body, params, query)
|
||||
3. Implements error handling
|
||||
4. Creates test file with request/response tests
|
||||
5. Follows REST/GraphQL conventions
|
||||
6. Includes authentication middleware (if needed)
|
||||
|
||||
## Execution Steps
|
||||
|
||||
### Step 1: Gather Endpoint Requirements
|
||||
|
||||
**Ask user for endpoint details**:
|
||||
```
|
||||
Endpoint path: [e.g., /api/users/:id]
|
||||
HTTP method: [GET, POST, PUT, PATCH, DELETE]
|
||||
Resource name: [e.g., User, Post, Product]
|
||||
|
||||
Framework:
|
||||
- express (default)
|
||||
- fastify
|
||||
- nestjs
|
||||
- graphql
|
||||
|
||||
Authentication required: [yes/no]
|
||||
Request validation needed: [yes/no]
|
||||
```
|
||||
|
||||
**Validate endpoint path**:
|
||||
- Use predefined function: `functions/route_validator.py`
|
||||
- Ensure RESTful conventions
|
||||
- Check path parameters syntax
|
||||
- No trailing slashes
|
||||
|
||||
### Step 2: Generate Route Handler
|
||||
|
||||
**Based on HTTP method and framework**:
|
||||
|
||||
Use predefined function: `functions/endpoint_generator.py`
|
||||
|
||||
```bash
|
||||
python3 functions/endpoint_generator.py \
|
||||
--path "/api/users/:id" \
|
||||
--method "GET" \
|
||||
--resource "User" \
|
||||
--framework "express" \
|
||||
--auth true \
|
||||
--validation true \
|
||||
--template "templates/express-route-template.ts" \
|
||||
--output "src/routes/users.ts"
|
||||
```
|
||||
|
||||
**Template includes**:
|
||||
- Route definition
|
||||
- Request validation middleware
|
||||
- Controller/handler function
|
||||
- Error handling
|
||||
- Response formatting
|
||||
- TypeScript types
|
||||
|
||||
### Step 3: Generate Validation Schema
|
||||
|
||||
**Use predefined function**: `functions/validation_generator.py`
|
||||
|
||||
```bash
|
||||
python3 functions/validation_generator.py \
|
||||
--resource "User" \
|
||||
--method "POST" \
|
||||
--fields "name:string:required,email:email:required,age:number:optional" \
|
||||
--library "zod" \
|
||||
--output "src/validators/user.validator.ts"
|
||||
```
|
||||
|
||||
**Supported validation libraries**:
|
||||
- Zod (default, TypeScript-first)
|
||||
- Joi (JavaScript schema)
|
||||
- Yup (object schema)
|
||||
- Express-validator (middleware-based)
|
||||
|
||||
**Output example (Zod)**:
|
||||
```typescript
|
||||
import { z } from 'zod';
|
||||
|
||||
export const createUserSchema = z.object({
|
||||
name: z.string().min(1),
|
||||
email: z.string().email(),
|
||||
age: z.number().optional(),
|
||||
});
|
||||
|
||||
export type CreateUserInput = z.infer<typeof createUserSchema>;
|
||||
```
|
||||
|
||||
### Step 4: Generate Error Handling Middleware
|
||||
|
||||
**Use predefined function**: `functions/error_handler_generator.py`
|
||||
|
||||
```bash
|
||||
python3 functions/error_handler_generator.py \
|
||||
--framework "express" \
|
||||
--template "templates/error-handler-template.ts" \
|
||||
--output "src/middleware/errorHandler.ts"
|
||||
```
|
||||
|
||||
**Error handler includes**:
|
||||
- HTTP status code mapping
|
||||
- Error response formatting
|
||||
- Logging integration
|
||||
- Development vs production modes
|
||||
- Validation error handling
|
||||
|
||||
### Step 5: Generate Test File
|
||||
|
||||
**Use predefined function**: `functions/test_generator.py`
|
||||
|
||||
```bash
|
||||
python3 functions/test_generator.py \
|
||||
--endpoint "/api/users/:id" \
|
||||
--method "GET" \
|
||||
--framework "express" \
|
||||
--template "templates/endpoint-test-template.spec.ts" \
|
||||
--output "tests/routes/users.test.ts"
|
||||
```
|
||||
|
||||
**Test template includes**:
|
||||
- Success case (200/201)
|
||||
- Validation errors (400)
|
||||
- Not found (404)
|
||||
- Unauthorized (401)
|
||||
- Server errors (500)
|
||||
- Edge cases
|
||||
|
||||
**Example test**:
|
||||
```typescript
|
||||
describe('GET /api/users/:id', () => {
|
||||
it('returns user when found', async () => {
|
||||
const response = await request(app)
|
||||
.get('/api/users/123')
|
||||
.expect(200);
|
||||
|
||||
expect(response.body).toMatchObject({
|
||||
id: '123',
|
||||
name: expect.any(String),
|
||||
});
|
||||
});
|
||||
|
||||
it('returns 404 when user not found', async () => {
|
||||
await request(app)
|
||||
.get('/api/users/999')
|
||||
.expect(404);
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
### Step 6: Generate API Documentation Comment
|
||||
|
||||
**JSDoc or OpenAPI annotation**:
|
||||
|
||||
```typescript
|
||||
/**
|
||||
* @route GET /api/users/:id
|
||||
* @description Get user by ID
|
||||
* @access Private
|
||||
* @param {string} id - User ID
|
||||
* @returns {User} User object
|
||||
* @throws {404} User not found
|
||||
* @throws {401} Unauthorized
|
||||
*/
|
||||
```
|
||||
|
||||
### Step 7: Show Endpoint Summary
|
||||
|
||||
**Display generated files and usage**:
|
||||
|
||||
```
|
||||
✅ Endpoint Created: GET /api/users/:id
|
||||
|
||||
Structure:
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
📁 src/
|
||||
├── routes/users.ts (Route handler)
|
||||
├── validators/user.validator.ts (Request validation)
|
||||
└── middleware/errorHandler.ts (Error handling)
|
||||
|
||||
📁 tests/
|
||||
└── routes/users.test.ts (Integration tests)
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
|
||||
Route Registration:
|
||||
import { userRoutes } from './routes/users';
|
||||
app.use('/api', userRoutes);
|
||||
|
||||
Test:
|
||||
curl http://localhost:3000/api/users/123
|
||||
# or
|
||||
npm test -- users.test.ts
|
||||
|
||||
Next Steps:
|
||||
1. Implement business logic in controller
|
||||
2. Connect to database/service layer
|
||||
3. Run tests: npm test
|
||||
4. Test with Postman/Thunder Client
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Predefined Functions
|
||||
|
||||
### 1. route_validator.py
|
||||
|
||||
Validates route path follows REST conventions.
|
||||
|
||||
**Usage**:
|
||||
```bash
|
||||
python3 functions/route_validator.py --path "/api/users/:id" --method "GET"
|
||||
```
|
||||
|
||||
**Checks**:
|
||||
- RESTful naming (plural resources)
|
||||
- Path parameter syntax (`:id` or `{id}`)
|
||||
- No trailing slashes
|
||||
- HTTP method matches intent
|
||||
|
||||
**Returns**: Valid path or error message
|
||||
|
||||
---
|
||||
|
||||
### 2. endpoint_generator.py
|
||||
|
||||
Generates endpoint handler from template.
|
||||
|
||||
**Usage**:
|
||||
```bash
|
||||
python3 functions/endpoint_generator.py \
|
||||
--path "/api/users/:id" \
|
||||
--method "GET" \
|
||||
--resource "User" \
|
||||
--framework "express" \
|
||||
--auth true \
|
||||
--validation true \
|
||||
--template "templates/express-route-template.ts"
|
||||
```
|
||||
|
||||
**Parameters**:
|
||||
- `--path`: API endpoint path
|
||||
- `--method`: HTTP method
|
||||
- `--resource`: Resource name (singular, PascalCase)
|
||||
- `--framework`: Backend framework
|
||||
- `--auth`: Include auth middleware
|
||||
- `--validation`: Include validation middleware
|
||||
- `--template`: Template file path
|
||||
|
||||
**Returns**: Generated endpoint code
|
||||
|
||||
---
|
||||
|
||||
### 3. validation_generator.py
|
||||
|
||||
Generates request validation schema.
|
||||
|
||||
**Usage**:
|
||||
```bash
|
||||
python3 functions/validation_generator.py \
|
||||
--resource "User" \
|
||||
--method "POST" \
|
||||
--fields "name:string:required,email:email:required" \
|
||||
--library "zod"
|
||||
```
|
||||
|
||||
**Supported field types**:
|
||||
- `string`, `number`, `boolean`
|
||||
- `email`, `url`, `uuid`
|
||||
- `array`, `object`
|
||||
- `date`, `datetime`
|
||||
|
||||
**Returns**: Validation schema code
|
||||
|
||||
---
|
||||
|
||||
### 4. error_handler_generator.py
|
||||
|
||||
Generates error handling middleware.
|
||||
|
||||
**Usage**:
|
||||
```bash
|
||||
python3 functions/error_handler_generator.py \
|
||||
--framework "express" \
|
||||
--template "templates/error-handler-template.ts"
|
||||
```
|
||||
|
||||
**Returns**: Error handler middleware code
|
||||
|
||||
---
|
||||
|
||||
### 5. test_generator.py
|
||||
|
||||
Generates endpoint integration tests.
|
||||
|
||||
**Usage**:
|
||||
```bash
|
||||
python3 functions/test_generator.py \
|
||||
--endpoint "/api/users/:id" \
|
||||
--method "GET" \
|
||||
--framework "express" \
|
||||
--template "templates/endpoint-test-template.spec.ts"
|
||||
```
|
||||
|
||||
**Generates tests for**:
|
||||
- Success responses
|
||||
- Validation errors
|
||||
- Authentication errors
|
||||
- Not found errors
|
||||
- Server errors
|
||||
|
||||
**Returns**: Generated test code
|
||||
|
||||
---
|
||||
|
||||
## Templates
|
||||
|
||||
### express-route-template.ts
|
||||
|
||||
Express.js route handler template.
|
||||
|
||||
**Placeholders**:
|
||||
- `${ROUTE_PATH}` - API endpoint path
|
||||
- `${HTTP_METHOD}` - HTTP method (lowercase)
|
||||
- `${RESOURCE_NAME}` - Resource name (PascalCase)
|
||||
- `${VALIDATION_MIDDLEWARE}` - Validation middleware
|
||||
- `${AUTH_MIDDLEWARE}` - Authentication middleware
|
||||
|
||||
### fastify-route-template.ts
|
||||
|
||||
Fastify route handler template (alternative).
|
||||
|
||||
### graphql-resolver-template.ts
|
||||
|
||||
GraphQL resolver template (alternative).
|
||||
|
||||
### validation-zod-template.ts
|
||||
|
||||
Zod validation schema template.
|
||||
|
||||
### endpoint-test-template.spec.ts
|
||||
|
||||
Integration test template with supertest.
|
||||
|
||||
**Placeholders**:
|
||||
- `${ENDPOINT_PATH}` - Endpoint to test
|
||||
- `${HTTP_METHOD}` - HTTP method
|
||||
- `${TEST_CASES}` - Generated test cases
|
||||
|
||||
---
|
||||
|
||||
## Examples
|
||||
|
||||
See `examples/` directory for reference implementations:
|
||||
|
||||
1. **users-get.ts** - GET endpoint with auth
|
||||
2. **users-post.ts** - POST endpoint with validation
|
||||
3. **graphql-resolver.ts** - GraphQL mutation example
|
||||
|
||||
Each example includes:
|
||||
- Route/resolver implementation
|
||||
- Validation schema
|
||||
- Error handling
|
||||
- Test file
|
||||
- Usage documentation
|
||||
|
||||
---
|
||||
|
||||
## Best Practices
|
||||
|
||||
### REST API Design
|
||||
- **Use plural nouns** for resources (`/users`, not `/user`)
|
||||
- **Use HTTP methods correctly** (GET=read, POST=create, PUT/PATCH=update, DELETE=remove)
|
||||
- **Nest resources properly** (`/users/:userId/posts/:postId`)
|
||||
- **Return proper status codes** (200, 201, 400, 401, 404, 500)
|
||||
|
||||
### Request Validation
|
||||
- **Validate all inputs** (body, params, query)
|
||||
- **Fail fast** (validate before business logic)
|
||||
- **Clear error messages** (tell user what's wrong)
|
||||
- **Sanitize inputs** (prevent injection attacks)
|
||||
|
||||
### Error Handling
|
||||
- **Centralized error handler** (DRY principle)
|
||||
- **Consistent error format** (always same structure)
|
||||
- **Don't expose internals** (sanitize stack traces in production)
|
||||
- **Log errors** (for debugging)
|
||||
|
||||
### Security
|
||||
- **Authentication** (verify identity)
|
||||
- **Authorization** (check permissions)
|
||||
- **Rate limiting** (prevent abuse)
|
||||
- **Input sanitization** (prevent XSS, SQL injection)
|
||||
|
||||
### Testing
|
||||
- **Test happy path** (success cases)
|
||||
- **Test error cases** (validation, auth, not found)
|
||||
- **Test edge cases** (empty data, large data)
|
||||
- **Mock external dependencies** (database, APIs)
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Route Not Found (404)
|
||||
|
||||
**Problem**: Endpoint returns 404 even though route is defined
|
||||
|
||||
**Solutions**:
|
||||
1. Check route registration order (specific before generic)
|
||||
2. Verify path matches exactly (case-sensitive)
|
||||
3. Check middleware isn't blocking request
|
||||
4. Validate HTTP method matches
|
||||
|
||||
### Validation Always Fails
|
||||
|
||||
**Problem**: Valid requests fail validation
|
||||
|
||||
**Solutions**:
|
||||
1. Check field names match exactly
|
||||
2. Verify data types are correct
|
||||
3. Check required vs optional fields
|
||||
4. Inspect validation error message
|
||||
|
||||
### Tests Failing
|
||||
|
||||
**Problem**: Integration tests don't pass
|
||||
|
||||
**Solutions**:
|
||||
1. Ensure test database is seeded
|
||||
2. Check test fixtures are correct
|
||||
3. Verify mocks are set up properly
|
||||
4. Run tests with `--verbose` flag
|
||||
|
||||
---
|
||||
|
||||
## Success Criteria
|
||||
|
||||
**This skill succeeds when**:
|
||||
- [ ] Endpoint responds with correct status codes
|
||||
- [ ] Request validation catches invalid inputs
|
||||
- [ ] Error handling works consistently
|
||||
- [ ] Tests cover success and error cases
|
||||
- [ ] Code follows REST/GraphQL conventions
|
||||
- [ ] Documentation is clear and complete
|
||||
|
||||
---
|
||||
|
||||
**Auto-invoke this skill when creating API endpoints to ensure consistency and security** 🔒
|
||||
54
skills/backend-endpoint/examples/users-get.ts
Normal file
54
skills/backend-endpoint/examples/users-get.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
/**
|
||||
* User Routes - GET endpoint example
|
||||
*
|
||||
* @route GET /api/users/:id
|
||||
* @access Private
|
||||
*/
|
||||
|
||||
import { Router, Request, Response, NextFunction } from 'express';
|
||||
import { authMiddleware } from '../middleware/auth';
|
||||
|
||||
const router = Router();
|
||||
|
||||
/**
|
||||
* GET /api/users/:id
|
||||
* @description Get user by ID
|
||||
* @access Private
|
||||
*/
|
||||
router.get(
|
||||
'/api/users/:id',
|
||||
authMiddleware,
|
||||
async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
// TODO: Fetch user from database
|
||||
const user = await getUserById(id);
|
||||
|
||||
if (!user) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
error: 'User not found',
|
||||
});
|
||||
}
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
data: user,
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
export default router;
|
||||
|
||||
// Mock function (replace with actual database query)
|
||||
async function getUserById(id: string) {
|
||||
return {
|
||||
id,
|
||||
name: 'John Doe',
|
||||
email: 'john@example.com',
|
||||
};
|
||||
}
|
||||
59
skills/backend-endpoint/examples/users-post.ts
Normal file
59
skills/backend-endpoint/examples/users-post.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
/**
|
||||
* User Routes - POST endpoint example with validation
|
||||
*
|
||||
* @route POST /api/users
|
||||
* @access Private
|
||||
*/
|
||||
|
||||
import { Router, Request, Response, NextFunction } from 'express';
|
||||
import { z } from 'zod';
|
||||
import { authMiddleware } from '../middleware/auth';
|
||||
import { validateRequest } from '../middleware/validation';
|
||||
|
||||
const router = Router();
|
||||
|
||||
// Validation schema
|
||||
const createUserSchema = z.object({
|
||||
name: z.string().min(1).max(100),
|
||||
email: z.string().email(),
|
||||
age: z.number().int().min(18).optional(),
|
||||
});
|
||||
|
||||
type CreateUserInput = z.infer<typeof createUserSchema>;
|
||||
|
||||
/**
|
||||
* POST /api/users
|
||||
* @description Create new user
|
||||
* @access Private
|
||||
*/
|
||||
router.post(
|
||||
'/api/users',
|
||||
authMiddleware,
|
||||
validateRequest(createUserSchema),
|
||||
async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const userData: CreateUserInput = req.body;
|
||||
|
||||
// TODO: Save user to database
|
||||
const newUser = await createUser(userData);
|
||||
|
||||
res.status(201).json({
|
||||
success: true,
|
||||
data: newUser,
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
export default router;
|
||||
|
||||
// Mock function (replace with actual database insert)
|
||||
async function createUser(data: CreateUserInput) {
|
||||
return {
|
||||
id: '123',
|
||||
...data,
|
||||
createdAt: new Date(),
|
||||
};
|
||||
}
|
||||
123
skills/backend-endpoint/functions/endpoint_generator.py
Executable file
123
skills/backend-endpoint/functions/endpoint_generator.py
Executable file
@@ -0,0 +1,123 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Generate backend API endpoint from template with substitutions.
|
||||
|
||||
Creates route handlers with authentication, validation, and error handling.
|
||||
"""
|
||||
|
||||
import sys
|
||||
import argparse
|
||||
import os
|
||||
|
||||
def read_template(template_path: str) -> str:
|
||||
"""Read template file content."""
|
||||
try:
|
||||
with open(template_path, 'r') as f:
|
||||
return f.read()
|
||||
except FileNotFoundError:
|
||||
raise FileNotFoundError(f"Template file not found: {template_path}")
|
||||
|
||||
def generate_endpoint(
|
||||
path: str,
|
||||
method: str,
|
||||
resource: str,
|
||||
framework: str,
|
||||
template_content: str,
|
||||
auth: bool = False,
|
||||
validation: bool = False
|
||||
) -> str:
|
||||
"""
|
||||
Generate endpoint code by substituting placeholders in template.
|
||||
|
||||
Args:
|
||||
path: API endpoint path
|
||||
method: HTTP method (GET, POST, etc.)
|
||||
resource: Resource name (PascalCase)
|
||||
framework: Backend framework (express, fastify, etc.)
|
||||
template_content: Template file content
|
||||
auth: Include authentication middleware
|
||||
validation: Include validation middleware
|
||||
|
||||
Returns:
|
||||
str: Generated endpoint code
|
||||
"""
|
||||
# Convert method to lowercase for handler name
|
||||
method_lower = method.lower()
|
||||
|
||||
# Generate middleware chain
|
||||
middlewares = []
|
||||
if auth:
|
||||
middlewares.append('authMiddleware')
|
||||
if validation:
|
||||
validator_name = f'validate{resource}'
|
||||
middlewares.append(validator_name)
|
||||
|
||||
middleware_chain = ', '.join(middlewares) if middlewares else ''
|
||||
|
||||
# Convert resource to different cases
|
||||
resource_lower = resource.lower()
|
||||
resource_plural = resource.lower() + 's' # Simple pluralization
|
||||
|
||||
# Perform substitutions
|
||||
substitutions = {
|
||||
'${ROUTE_PATH}': path,
|
||||
'${HTTP_METHOD}': method.upper(),
|
||||
'${HTTP_METHOD_LOWER}': method_lower,
|
||||
'${RESOURCE_NAME}': resource,
|
||||
'${RESOURCE_NAME_LOWER}': resource_lower,
|
||||
'${RESOURCE_NAME_PLURAL}': resource_plural,
|
||||
'${VALIDATION_MIDDLEWARE}': f'validate{resource}' if validation else '',
|
||||
'${AUTH_MIDDLEWARE}': 'authMiddleware' if auth else '',
|
||||
'${MIDDLEWARE_CHAIN}': middleware_chain,
|
||||
}
|
||||
|
||||
result = template_content
|
||||
for placeholder, value in substitutions.items():
|
||||
result = result.replace(placeholder, value)
|
||||
|
||||
return result
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description='Generate backend API endpoint from template')
|
||||
parser.add_argument('--path', required=True, help='API endpoint path')
|
||||
parser.add_argument('--method', required=True, choices=['GET', 'POST', 'PUT', 'PATCH', 'DELETE'], help='HTTP method')
|
||||
parser.add_argument('--resource', required=True, help='Resource name (PascalCase)')
|
||||
parser.add_argument('--framework', default='express', choices=['express', 'fastify', 'nestjs'], help='Backend framework')
|
||||
parser.add_argument('--auth', action='store_true', help='Include authentication middleware')
|
||||
parser.add_argument('--validation', action='store_true', help='Include validation middleware')
|
||||
parser.add_argument('--template', required=True, help='Template file path')
|
||||
parser.add_argument('--output', help='Output file path (optional, prints to stdout if not provided)')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
try:
|
||||
# Read template
|
||||
template_content = read_template(args.template)
|
||||
|
||||
# Generate endpoint
|
||||
endpoint_code = generate_endpoint(
|
||||
args.path,
|
||||
args.method,
|
||||
args.resource,
|
||||
args.framework,
|
||||
template_content,
|
||||
args.auth,
|
||||
args.validation
|
||||
)
|
||||
|
||||
# Output
|
||||
if args.output:
|
||||
os.makedirs(os.path.dirname(args.output), exist_ok=True)
|
||||
with open(args.output, 'w') as f:
|
||||
f.write(endpoint_code)
|
||||
print(f"✅ Endpoint generated: {args.output}")
|
||||
else:
|
||||
print(endpoint_code)
|
||||
|
||||
sys.exit(0)
|
||||
except Exception as e:
|
||||
print(f"❌ Error: {e}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
143
skills/backend-endpoint/functions/route_validator.py
Executable file
143
skills/backend-endpoint/functions/route_validator.py
Executable file
@@ -0,0 +1,143 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Validate API route path follows REST conventions.
|
||||
|
||||
Ensures routes are RESTful, properly formatted, and follow best practices.
|
||||
"""
|
||||
|
||||
import sys
|
||||
import re
|
||||
import argparse
|
||||
|
||||
# HTTP methods and their typical use cases
|
||||
HTTP_METHODS = {
|
||||
'GET': 'Retrieve resource(s)',
|
||||
'POST': 'Create new resource',
|
||||
'PUT': 'Replace entire resource',
|
||||
'PATCH': 'Update part of resource',
|
||||
'DELETE': 'Remove resource',
|
||||
}
|
||||
|
||||
def validate_route_path(path, method=None):
|
||||
"""
|
||||
Validate route path against REST conventions.
|
||||
|
||||
Args:
|
||||
path: API route path
|
||||
method: HTTP method (optional)
|
||||
|
||||
Returns:
|
||||
tuple: (is_valid: bool, error_message: str or None)
|
||||
"""
|
||||
# Check path starts with /
|
||||
if not path.startswith('/'):
|
||||
return False, "Route path must start with '/'"
|
||||
|
||||
# Check no trailing slash (except for root)
|
||||
if len(path) > 1 and path.endswith('/'):
|
||||
return False, "Route path should not end with '/' (except root '/')"
|
||||
|
||||
# Check for double slashes
|
||||
if '//' in path:
|
||||
return False, "Route path contains double slashes '//'"
|
||||
|
||||
# Check path segments
|
||||
segments = [s for s in path.split('/') if s]
|
||||
|
||||
# Check resource naming (should be plural for collections)
|
||||
for i, segment in enumerate(segments):
|
||||
# Skip API prefix and version
|
||||
if segment in ('api', 'v1', 'v2', 'v3'):
|
||||
continue
|
||||
|
||||
# Skip path parameters
|
||||
if segment.startswith(':') or (segment.startswith('{') and segment.endswith('}')):
|
||||
continue
|
||||
|
||||
# Check resource naming
|
||||
if not segment.islower():
|
||||
return False, f"Resource '{segment}' should be lowercase"
|
||||
|
||||
# Check for underscores vs hyphens (prefer hyphens)
|
||||
if '_' in segment:
|
||||
suggested = segment.replace('_', '-')
|
||||
return False, f"Use hyphens instead of underscores: '{segment}' → '{suggested}'"
|
||||
|
||||
# Method-specific validation
|
||||
if method:
|
||||
method = method.upper()
|
||||
if method not in HTTP_METHODS:
|
||||
return False, f"Invalid HTTP method: {method}. Use: {', '.join(HTTP_METHODS.keys())}"
|
||||
|
||||
# Check method matches path intent
|
||||
if method == 'POST' and segments and segments[-1].startswith(':'):
|
||||
return False, "POST endpoints should target collections, not specific resources (remove :id)"
|
||||
|
||||
if method in ('PUT', 'PATCH', 'DELETE'):
|
||||
# These methods typically need an ID parameter
|
||||
if not any(s.startswith(':') or (s.startswith('{') and s.endswith('}')) for s in segments):
|
||||
return False, f"{method} endpoints typically need a resource ID parameter (e.g., /:id)"
|
||||
|
||||
return True, None
|
||||
|
||||
def suggest_valid_path(path):
|
||||
"""
|
||||
Suggest a valid route path if the provided one is invalid.
|
||||
|
||||
Args:
|
||||
path: Invalid route path
|
||||
|
||||
Returns:
|
||||
str: Suggested valid path
|
||||
"""
|
||||
# Remove trailing slash
|
||||
if path.endswith('/') and len(path) > 1:
|
||||
path = path.rstrip('/')
|
||||
|
||||
# Fix double slashes
|
||||
while '//' in path:
|
||||
path = path.replace('//', '/')
|
||||
|
||||
# Convert to lowercase and replace underscores
|
||||
segments = path.split('/')
|
||||
fixed_segments = []
|
||||
for segment in segments:
|
||||
if segment.startswith(':') or (segment.startswith('{') and segment.endswith('}')):
|
||||
fixed_segments.append(segment)
|
||||
else:
|
||||
fixed_segments.append(segment.lower().replace('_', '-'))
|
||||
|
||||
suggested = '/'.join(fixed_segments)
|
||||
|
||||
# Ensure starts with /
|
||||
if not suggested.startswith('/'):
|
||||
suggested = '/' + suggested
|
||||
|
||||
return suggested
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description='Validate REST API route path')
|
||||
parser.add_argument('--path', required=True, help='Route path to validate')
|
||||
parser.add_argument('--method', help='HTTP method (GET, POST, PUT, PATCH, DELETE)')
|
||||
parser.add_argument('--suggest', action='store_true', help='Suggest a valid path if invalid')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
is_valid, error = validate_route_path(args.path, args.method)
|
||||
|
||||
if is_valid:
|
||||
print(f"✅ '{args.path}' is a valid route path")
|
||||
if args.method:
|
||||
print(f" Method: {args.method.upper()} - {HTTP_METHODS[args.method.upper()]}")
|
||||
sys.exit(0)
|
||||
else:
|
||||
print(f"❌ Invalid route path: {error}", file=sys.stderr)
|
||||
|
||||
if args.suggest:
|
||||
suggested = suggest_valid_path(args.path)
|
||||
print(f"💡 Suggested path: {suggested}", file=sys.stderr)
|
||||
|
||||
sys.exit(1)
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
120
skills/backend-endpoint/functions/validation_generator.py
Executable file
120
skills/backend-endpoint/functions/validation_generator.py
Executable file
@@ -0,0 +1,120 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Generate request validation schema.
|
||||
|
||||
Creates Zod/Joi/Yup validation schemas for API endpoints.
|
||||
"""
|
||||
|
||||
import sys
|
||||
import argparse
|
||||
|
||||
# Type mapping for different validation libraries
|
||||
ZOD_TYPE_MAP = {
|
||||
'string': 'z.string()',
|
||||
'number': 'z.number()',
|
||||
'boolean': 'z.boolean()',
|
||||
'email': 'z.string().email()',
|
||||
'url': 'z.string().url()',
|
||||
'uuid': 'z.string().uuid()',
|
||||
'date': 'z.date()',
|
||||
'array': 'z.array(z.any())',
|
||||
'object': 'z.object({})',
|
||||
}
|
||||
|
||||
def parse_field_spec(field_spec: str):
|
||||
"""
|
||||
Parse field specification.
|
||||
|
||||
Format: "fieldName:type:required" or "fieldName:type:optional"
|
||||
|
||||
Args:
|
||||
field_spec: Field specification string
|
||||
|
||||
Returns:
|
||||
tuple: (field_name, field_type, is_required)
|
||||
"""
|
||||
parts = field_spec.strip().split(':')
|
||||
|
||||
if len(parts) < 2:
|
||||
raise ValueError(f"Invalid field spec: '{field_spec}'. Expected format: 'name:type' or 'name:type:required'")
|
||||
|
||||
field_name = parts[0].strip()
|
||||
field_type = parts[1].strip().lower()
|
||||
is_required = len(parts) < 3 or parts[2].strip().lower() not in ('optional', 'opt', '?', 'false')
|
||||
|
||||
return field_name, field_type, is_required
|
||||
|
||||
def generate_zod_schema(resource: str, method: str, fields: list) -> str:
|
||||
"""
|
||||
Generate Zod validation schema.
|
||||
|
||||
Args:
|
||||
resource: Resource name (PascalCase)
|
||||
method: HTTP method
|
||||
fields: List of field specifications
|
||||
|
||||
Returns:
|
||||
str: Zod schema code
|
||||
"""
|
||||
schema_name = f"{method.lower()}{resource}Schema"
|
||||
type_name = f"{method.capitalize()}{resource}Input"
|
||||
|
||||
lines = [
|
||||
"import { z } from 'zod';\n",
|
||||
f"export const {schema_name} = z.object({{",
|
||||
]
|
||||
|
||||
for field_spec in fields:
|
||||
if not field_spec.strip():
|
||||
continue
|
||||
|
||||
field_name, field_type, is_required = parse_field_spec(field_spec)
|
||||
zod_type = ZOD_TYPE_MAP.get(field_type, 'z.any()')
|
||||
|
||||
if not is_required:
|
||||
zod_type += '.optional()'
|
||||
|
||||
lines.append(f" {field_name}: {zod_type},")
|
||||
|
||||
lines.append("});\n")
|
||||
lines.append(f"export type {type_name} = z.infer<typeof {schema_name}>;")
|
||||
|
||||
return '\n'.join(lines)
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description='Generate request validation schema')
|
||||
parser.add_argument('--resource', required=True, help='Resource name (PascalCase)')
|
||||
parser.add_argument('--method', required=True, help='HTTP method (GET, POST, etc.)')
|
||||
parser.add_argument('--fields', required=True, help='Comma-separated field specifications')
|
||||
parser.add_argument('--library', default='zod', choices=['zod', 'joi', 'yup'], help='Validation library')
|
||||
parser.add_argument('--output', help='Output file path (optional, prints to stdout if not provided)')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# Parse field specifications
|
||||
field_specs = [f.strip() for f in args.fields.split(',') if f.strip()]
|
||||
|
||||
try:
|
||||
if args.library == 'zod':
|
||||
schema_code = generate_zod_schema(args.resource, args.method, field_specs)
|
||||
else:
|
||||
print(f"❌ Library '{args.library}' not yet implemented. Use 'zod' for now.", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
# Output
|
||||
if args.output:
|
||||
import os
|
||||
os.makedirs(os.path.dirname(args.output), exist_ok=True)
|
||||
with open(args.output, 'w') as f:
|
||||
f.write(schema_code)
|
||||
print(f"✅ Validation schema generated: {args.output}")
|
||||
else:
|
||||
print(schema_code)
|
||||
|
||||
sys.exit(0)
|
||||
except ValueError as e:
|
||||
print(f"❌ Error: {e}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,6 @@
|
||||
import request from 'supertest';
|
||||
import app from '../app';
|
||||
|
||||
describe('${HTTP_METHOD} ${ENDPOINT_PATH}', () => {
|
||||
${TEST_CASES}
|
||||
});
|
||||
32
skills/backend-endpoint/templates/express-route-template.ts
Normal file
32
skills/backend-endpoint/templates/express-route-template.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* ${RESOURCE_NAME} Routes
|
||||
*
|
||||
* @route ${HTTP_METHOD} ${ROUTE_PATH}
|
||||
*/
|
||||
|
||||
import { Router, Request, Response, NextFunction } from 'express';
|
||||
|
||||
const router = Router();
|
||||
|
||||
/**
|
||||
* ${HTTP_METHOD} ${ROUTE_PATH}
|
||||
* @description ${HTTP_METHOD} ${RESOURCE_NAME_LOWER}
|
||||
*/
|
||||
router.${HTTP_METHOD_LOWER}(
|
||||
'${ROUTE_PATH}',
|
||||
${MIDDLEWARE_CHAIN ? MIDDLEWARE_CHAIN + ',' : ''}
|
||||
async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
// TODO: Implement ${RESOURCE_NAME_LOWER} ${HTTP_METHOD_LOWER} logic
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
data: {}, // Replace with actual data
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
export default router;
|
||||
Reference in New Issue
Block a user