Initial commit

This commit is contained in:
Zhongwei Li
2025-11-29 17:51:59 +08:00
commit 38e80921c8
89 changed files with 20480 additions and 0 deletions

View 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()

View 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()

View 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()