chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,239 @@
|
||||
# Git Workflow Helper
|
||||
|
||||
Manage Git workflows with best practices and common operations.
|
||||
|
||||
## Purpose
|
||||
|
||||
This command helps you perform common Git operations following best practices for collaborative development.
|
||||
|
||||
## Usage
|
||||
|
||||
```
|
||||
/git-workflow
|
||||
```
|
||||
|
||||
## What this command does
|
||||
|
||||
1. **Guides through Git operations** with proper workflow
|
||||
2. **Suggests best practices** for commits and branches
|
||||
3. **Helps with conflict resolution** and merging
|
||||
4. **Provides templates** for commit messages
|
||||
5. **Manages branching strategies** (Git Flow, GitHub Flow)
|
||||
|
||||
## Common Workflows
|
||||
|
||||
### Feature Development
|
||||
```bash
|
||||
# Create and switch to feature branch
|
||||
git checkout -b feature/user-authentication
|
||||
|
||||
# Work on your feature
|
||||
# ... make changes ...
|
||||
|
||||
# Stage and commit changes
|
||||
git add .
|
||||
git commit -m "feat: add user authentication system
|
||||
|
||||
- Implement login/logout functionality
|
||||
- Add password validation
|
||||
- Create user session management
|
||||
- Add authentication middleware"
|
||||
|
||||
# Push feature branch
|
||||
git push -u origin feature/user-authentication
|
||||
|
||||
# Create pull request (via GitHub/GitLab interface)
|
||||
```
|
||||
|
||||
### Hotfix Workflow
|
||||
```bash
|
||||
# Create hotfix branch from main
|
||||
git checkout main
|
||||
git checkout -b hotfix/security-patch
|
||||
|
||||
# Fix the issue
|
||||
# ... make changes ...
|
||||
|
||||
# Commit the fix
|
||||
git commit -m "fix: resolve security vulnerability in auth module
|
||||
|
||||
- Patch XSS vulnerability in login form
|
||||
- Update input validation
|
||||
- Add CSRF protection"
|
||||
|
||||
# Push and create urgent PR
|
||||
git push -u origin hotfix/security-patch
|
||||
```
|
||||
|
||||
### Sync with Remote
|
||||
```bash
|
||||
# Update main branch
|
||||
git checkout main
|
||||
git pull origin main
|
||||
|
||||
# Update feature branch with latest main
|
||||
git checkout feature/your-feature
|
||||
git rebase main
|
||||
# OR
|
||||
git merge main
|
||||
```
|
||||
|
||||
## Commit Message Conventions
|
||||
|
||||
### Conventional Commits Format
|
||||
```
|
||||
<type>[optional scope]: <description>
|
||||
|
||||
[optional body]
|
||||
|
||||
[optional footer(s)]
|
||||
```
|
||||
|
||||
### Common Types
|
||||
- **feat**: New feature
|
||||
- **fix**: Bug fix
|
||||
- **docs**: Documentation changes
|
||||
- **style**: Code style changes (formatting, etc.)
|
||||
- **refactor**: Code refactoring
|
||||
- **test**: Adding or updating tests
|
||||
- **chore**: Maintenance tasks
|
||||
|
||||
### Examples
|
||||
```bash
|
||||
# Feature
|
||||
git commit -m "feat(auth): add OAuth2 integration"
|
||||
|
||||
# Bug fix
|
||||
git commit -m "fix(api): handle null response in user endpoint"
|
||||
|
||||
# Documentation
|
||||
git commit -m "docs: update API documentation for v2.0"
|
||||
|
||||
# Breaking change
|
||||
git commit -m "feat!: change API response format
|
||||
|
||||
BREAKING CHANGE: API responses now use 'data' wrapper"
|
||||
```
|
||||
|
||||
## Branch Management
|
||||
|
||||
### Git Flow Strategy
|
||||
```bash
|
||||
# Main branches
|
||||
main # Production-ready code
|
||||
develop # Integration branch
|
||||
|
||||
# Supporting branches
|
||||
feature/* # New features
|
||||
release/* # Release preparation
|
||||
hotfix/* # Quick fixes to production
|
||||
```
|
||||
|
||||
### GitHub Flow (Simplified)
|
||||
```bash
|
||||
# Only main branch + feature branches
|
||||
main # Production-ready code
|
||||
feature/* # All new work
|
||||
```
|
||||
|
||||
## Conflict Resolution
|
||||
|
||||
### When Conflicts Occur
|
||||
```bash
|
||||
# Start merge/rebase
|
||||
git merge feature-branch
|
||||
# OR
|
||||
git rebase main
|
||||
|
||||
# If conflicts occur, Git will list conflicted files
|
||||
# Edit each file to resolve conflicts
|
||||
|
||||
# Mark conflicts as resolved
|
||||
git add conflicted-file.js
|
||||
|
||||
# Continue the merge/rebase
|
||||
git merge --continue
|
||||
# OR
|
||||
git rebase --continue
|
||||
```
|
||||
|
||||
### Conflict Markers
|
||||
```javascript
|
||||
<<<<<<< HEAD
|
||||
// Your current branch code
|
||||
const user = getCurrentUser();
|
||||
=======
|
||||
// Incoming branch code
|
||||
const user = getAuthenticatedUser();
|
||||
>>>>>>> feature-branch
|
||||
```
|
||||
|
||||
## Useful Git Commands
|
||||
|
||||
### Status and Information
|
||||
```bash
|
||||
git status # Check working directory status
|
||||
git log --oneline # View commit history
|
||||
git branch -a # List all branches
|
||||
git remote -v # List remote repositories
|
||||
```
|
||||
|
||||
### Undoing Changes
|
||||
```bash
|
||||
git checkout -- file.js # Discard changes to file
|
||||
git reset HEAD file.js # Unstage file
|
||||
git reset --soft HEAD~1 # Undo last commit (keep changes)
|
||||
git reset --hard HEAD~1 # Undo last commit (discard changes)
|
||||
```
|
||||
|
||||
### Stashing Work
|
||||
```bash
|
||||
git stash # Save current work
|
||||
git stash pop # Apply and remove latest stash
|
||||
git stash list # List all stashes
|
||||
git stash apply stash@{1} # Apply specific stash
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Commit Often** - Make small, focused commits
|
||||
2. **Write Clear Messages** - Use conventional commit format
|
||||
3. **Test Before Committing** - Ensure code works
|
||||
4. **Pull Before Push** - Keep history clean
|
||||
5. **Use Branches** - Don't work directly on main
|
||||
6. **Review Code** - Use pull requests for collaboration
|
||||
7. **Keep History Clean** - Rebase feature branches when appropriate
|
||||
|
||||
## Git Hooks (Optional)
|
||||
|
||||
### Pre-commit Hook
|
||||
```bash
|
||||
#!/bin/sh
|
||||
# .git/hooks/pre-commit
|
||||
|
||||
# Run linter
|
||||
npm run lint
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "Linting failed. Please fix errors before committing."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Run tests
|
||||
npm test
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "Tests failed. Please fix tests before committing."
|
||||
exit 1
|
||||
fi
|
||||
```
|
||||
|
||||
### Commit Message Hook
|
||||
```bash
|
||||
#!/bin/sh
|
||||
# .git/hooks/commit-msg
|
||||
|
||||
# Check commit message format
|
||||
if ! grep -qE "^(feat|fix|docs|style|refactor|test|chore)(\(.+\))?: .{1,50}" "$1"; then
|
||||
echo "Invalid commit message format. Use conventional commits."
|
||||
exit 1
|
||||
fi
|
||||
```
|
||||
@@ -0,0 +1,316 @@
|
||||
# Project Setup Helper
|
||||
|
||||
Set up new projects with proper structure and best practices.
|
||||
|
||||
## Purpose
|
||||
|
||||
This command helps you set up new projects with proper directory structure, configuration files, and development environment.
|
||||
|
||||
## Usage
|
||||
|
||||
```
|
||||
/project-setup
|
||||
```
|
||||
|
||||
## What this command does
|
||||
|
||||
1. **Creates project structure** with standard directories
|
||||
2. **Sets up configuration files** (.gitignore, README, etc.)
|
||||
3. **Initializes version control** and development tools
|
||||
4. **Configures environment files** and dependencies
|
||||
5. **Follows language-specific** conventions and best practices
|
||||
|
||||
## Project Structure Templates
|
||||
|
||||
### Generic Project Structure
|
||||
```
|
||||
project-name/
|
||||
├── README.md # Project documentation
|
||||
├── .gitignore # Git ignore rules
|
||||
├── .env.example # Environment variables template
|
||||
├── LICENSE # Project license
|
||||
├── CHANGELOG.md # Version history
|
||||
├── docs/ # Documentation
|
||||
│ ├── API.md
|
||||
│ ├── CONTRIBUTING.md
|
||||
│ └── DEPLOYMENT.md
|
||||
├── src/ # Source code
|
||||
│ ├── main/
|
||||
│ ├── utils/
|
||||
│ └── config/
|
||||
├── tests/ # Test files
|
||||
│ ├── unit/
|
||||
│ ├── integration/
|
||||
│ └── fixtures/
|
||||
├── scripts/ # Build and deployment scripts
|
||||
│ ├── build.sh
|
||||
│ ├── deploy.sh
|
||||
│ └── setup.sh
|
||||
└── config/ # Configuration files
|
||||
├── development.yml
|
||||
├── production.yml
|
||||
└── testing.yml
|
||||
```
|
||||
|
||||
### Web Application Structure
|
||||
```
|
||||
web-app/
|
||||
├── public/ # Static assets
|
||||
│ ├── index.html
|
||||
│ ├── favicon.ico
|
||||
│ └── assets/
|
||||
│ ├── css/
|
||||
│ ├── js/
|
||||
│ └── images/
|
||||
├── src/ # Source code
|
||||
│ ├── components/ # Reusable components
|
||||
│ ├── pages/ # Page components
|
||||
│ ├── services/ # API services
|
||||
│ ├── utils/ # Utility functions
|
||||
│ ├── styles/ # Stylesheets
|
||||
│ └── config/ # Configuration
|
||||
├── tests/ # Test files
|
||||
└── build/ # Build output
|
||||
```
|
||||
|
||||
### API Project Structure
|
||||
```
|
||||
api-project/
|
||||
├── src/
|
||||
│ ├── controllers/ # Request handlers
|
||||
│ ├── models/ # Data models
|
||||
│ ├── services/ # Business logic
|
||||
│ ├── middleware/ # Custom middleware
|
||||
│ ├── routes/ # API routes
|
||||
│ ├── utils/ # Utility functions
|
||||
│ └── config/ # Configuration
|
||||
├── tests/
|
||||
│ ├── unit/
|
||||
│ ├── integration/
|
||||
│ └── e2e/
|
||||
├── docs/
|
||||
│ ├── api-spec.yml # OpenAPI specification
|
||||
│ └── README.md
|
||||
└── scripts/
|
||||
├── seed-db.js # Database seeding
|
||||
└── migrate.js # Database migrations
|
||||
```
|
||||
|
||||
## Essential Configuration Files
|
||||
|
||||
### .gitignore Template
|
||||
```gitignore
|
||||
# Dependencies
|
||||
node_modules/
|
||||
*.log
|
||||
npm-debug.log*
|
||||
|
||||
# Environment files
|
||||
.env
|
||||
.env.local
|
||||
.env.production
|
||||
|
||||
# Build outputs
|
||||
dist/
|
||||
build/
|
||||
*.tgz
|
||||
|
||||
# IDE files
|
||||
.vscode/
|
||||
.idea/
|
||||
*.swp
|
||||
*.swo
|
||||
|
||||
# OS files
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# Temporary files
|
||||
*.tmp
|
||||
*.temp
|
||||
```
|
||||
|
||||
### README.md Template
|
||||
```markdown
|
||||
# Project Name
|
||||
|
||||
Brief description of what this project does.
|
||||
|
||||
## Features
|
||||
|
||||
- Feature 1
|
||||
- Feature 2
|
||||
- Feature 3
|
||||
|
||||
## Installation
|
||||
|
||||
\`\`\`bash
|
||||
# Clone the repository
|
||||
git clone https://github.com/username/project-name.git
|
||||
|
||||
# Install dependencies
|
||||
npm install
|
||||
|
||||
# Copy environment file
|
||||
cp .env.example .env
|
||||
\`\`\`
|
||||
|
||||
## Usage
|
||||
|
||||
\`\`\`bash
|
||||
# Start development server
|
||||
npm run dev
|
||||
|
||||
# Run tests
|
||||
npm test
|
||||
|
||||
# Build for production
|
||||
npm run build
|
||||
\`\`\`
|
||||
|
||||
## API Documentation
|
||||
|
||||
[API documentation](docs/API.md)
|
||||
|
||||
## Contributing
|
||||
|
||||
Please read [CONTRIBUTING.md](docs/CONTRIBUTING.md) for details.
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed under the MIT License - see [LICENSE](LICENSE) file.
|
||||
```
|
||||
|
||||
### .env.example Template
|
||||
```env
|
||||
# Application
|
||||
NODE_ENV=development
|
||||
PORT=3000
|
||||
HOST=localhost
|
||||
|
||||
# Database
|
||||
DB_HOST=localhost
|
||||
DB_PORT=5432
|
||||
DB_NAME=myapp
|
||||
DB_USER=username
|
||||
DB_PASSWORD=password
|
||||
|
||||
# API Keys
|
||||
API_KEY=your-api-key-here
|
||||
SECRET_KEY=your-secret-key-here
|
||||
|
||||
# External Services
|
||||
REDIS_URL=redis://localhost:6379
|
||||
EMAIL_SERVICE_API_KEY=your-email-key
|
||||
```
|
||||
|
||||
## Development Environment Setup
|
||||
|
||||
### Package.json Scripts
|
||||
```json
|
||||
{
|
||||
"scripts": {
|
||||
"start": "node src/index.js",
|
||||
"dev": "nodemon src/index.js",
|
||||
"test": "jest",
|
||||
"test:watch": "jest --watch",
|
||||
"test:coverage": "jest --coverage",
|
||||
"lint": "eslint src/",
|
||||
"lint:fix": "eslint src/ --fix",
|
||||
"format": "prettier --write src/",
|
||||
"build": "webpack --mode production",
|
||||
"build:dev": "webpack --mode development",
|
||||
"clean": "rm -rf dist/"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Git Initialization
|
||||
```bash
|
||||
# Initialize Git repository
|
||||
git init
|
||||
|
||||
# Add initial files
|
||||
git add .
|
||||
|
||||
# Create initial commit
|
||||
git commit -m "feat: initial project setup
|
||||
|
||||
- Add project structure
|
||||
- Configure development environment
|
||||
- Add documentation templates
|
||||
- Set up testing framework"
|
||||
|
||||
# Add remote origin
|
||||
git remote add origin https://github.com/username/project-name.git
|
||||
|
||||
# Push to remote
|
||||
git push -u origin main
|
||||
```
|
||||
|
||||
## Language-Specific Setup
|
||||
|
||||
### Node.js/JavaScript
|
||||
```bash
|
||||
npm init -y
|
||||
npm install --save-dev nodemon jest eslint prettier
|
||||
```
|
||||
|
||||
### Python
|
||||
```bash
|
||||
python -m venv venv
|
||||
source venv/bin/activate # On Windows: venv\Scripts\activate
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
### Docker Setup
|
||||
```dockerfile
|
||||
# Dockerfile
|
||||
FROM node:16-alpine
|
||||
WORKDIR /app
|
||||
COPY package*.json ./
|
||||
RUN npm install
|
||||
COPY . .
|
||||
EXPOSE 3000
|
||||
CMD ["npm", "start"]
|
||||
```
|
||||
|
||||
```yaml
|
||||
# docker-compose.yml
|
||||
version: '3.8'
|
||||
services:
|
||||
app:
|
||||
build: .
|
||||
ports:
|
||||
- "3000:3000"
|
||||
environment:
|
||||
- NODE_ENV=development
|
||||
volumes:
|
||||
- .:/app
|
||||
- /app/node_modules
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Consistent Structure** - Follow established conventions
|
||||
2. **Clear Documentation** - Write comprehensive README
|
||||
3. **Environment Variables** - Keep secrets out of code
|
||||
4. **Version Control** - Initialize Git from the start
|
||||
5. **Testing Setup** - Configure testing from day one
|
||||
6. **Code Quality** - Set up linting and formatting
|
||||
7. **CI/CD Ready** - Structure for automated deployment
|
||||
8. **Security** - Include security best practices
|
||||
|
||||
## Checklist
|
||||
|
||||
- [ ] Create project directory structure
|
||||
- [ ] Initialize version control (Git)
|
||||
- [ ] Set up package manager (npm, pip, etc.)
|
||||
- [ ] Create README.md with project info
|
||||
- [ ] Add .gitignore file
|
||||
- [ ] Set up environment variables
|
||||
- [ ] Configure linting and formatting
|
||||
- [ ] Set up testing framework
|
||||
- [ ] Create basic documentation
|
||||
- [ ] Add license file
|
||||
- [ ] Set up CI/CD configuration (if needed)
|
||||
@@ -0,0 +1,41 @@
|
||||
{
|
||||
"mcpServers": {
|
||||
"memory-bank": {
|
||||
"name": "Memory Bank MCP",
|
||||
"description": "Centralized memory system for AI agents",
|
||||
"command": "server-memory",
|
||||
"args": [],
|
||||
"env": {}
|
||||
},
|
||||
"sequential-thinking": {
|
||||
"name": "Sequential Thinking MCP",
|
||||
"description": "Helps LLMs decompose complex tasks into logical steps",
|
||||
"command": "code-reasoning",
|
||||
"args": [],
|
||||
"env": {}
|
||||
},
|
||||
"brave-search": {
|
||||
"name": "Brave Search MCP",
|
||||
"description": "Privacy-focused web search tool",
|
||||
"command": "server-brave-search",
|
||||
"args": [],
|
||||
"env": {}
|
||||
},
|
||||
"google-maps": {
|
||||
"name": "Google Maps MCP",
|
||||
"description": "Integrates Google Maps for geolocation and directions",
|
||||
"command": "server-google-maps",
|
||||
"args": [],
|
||||
"env": {
|
||||
"GOOGLE_MAPS_API_KEY": "..."
|
||||
}
|
||||
},
|
||||
"deep-graph": {
|
||||
"name": "Deep Graph MCP (Code Graph)",
|
||||
"description": "Transforms source code into semantic graphs via DeepGraph",
|
||||
"command": "mcp-code-graph",
|
||||
"args": [],
|
||||
"env": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
# CLAUDE.md
|
||||
|
||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||
|
||||
## Universal Development Guidelines
|
||||
|
||||
### Code Quality Standards
|
||||
- Write clean, readable, and maintainable code
|
||||
- Follow consistent naming conventions across the project
|
||||
- Use meaningful variable and function names
|
||||
- Keep functions focused and single-purpose
|
||||
- Add comments for complex logic and business rules
|
||||
|
||||
### Git Workflow
|
||||
- Use descriptive commit messages following conventional commits format
|
||||
- Create feature branches for new development
|
||||
- Keep commits atomic and focused on single changes
|
||||
- Use pull requests for code review before merging
|
||||
- Maintain a clean commit history
|
||||
|
||||
### Documentation
|
||||
- Keep README.md files up to date
|
||||
- Document public APIs and interfaces
|
||||
- Include usage examples for complex features
|
||||
- Maintain inline code documentation
|
||||
- Update documentation when making changes
|
||||
|
||||
### Testing Approach
|
||||
- Write tests for new features and bug fixes
|
||||
- Maintain good test coverage
|
||||
- Use descriptive test names that explain the expected behavior
|
||||
- Organize tests logically by feature or module
|
||||
- Run tests before committing changes
|
||||
|
||||
### Security Best Practices
|
||||
- Never commit sensitive information (API keys, passwords, tokens)
|
||||
- Use environment variables for configuration
|
||||
- Validate input data and sanitize outputs
|
||||
- Follow principle of least privilege
|
||||
- Keep dependencies updated
|
||||
|
||||
## Project Structure Guidelines
|
||||
|
||||
### File Organization
|
||||
- Group related files in logical directories
|
||||
- Use consistent file and folder naming conventions
|
||||
- Separate source code from configuration files
|
||||
- Keep build artifacts out of version control
|
||||
- Organize assets and resources appropriately
|
||||
|
||||
### Configuration Management
|
||||
- Use configuration files for environment-specific settings
|
||||
- Centralize configuration in dedicated files
|
||||
- Use environment variables for sensitive or environment-specific data
|
||||
- Document configuration options and their purposes
|
||||
- Provide example configuration files
|
||||
|
||||
## Development Workflow
|
||||
|
||||
### Before Starting Work
|
||||
1. Pull latest changes from main branch
|
||||
2. Create a new feature branch
|
||||
3. Review existing code and architecture
|
||||
4. Plan the implementation approach
|
||||
|
||||
### During Development
|
||||
1. Make incremental commits with clear messages
|
||||
2. Run tests frequently to catch issues early
|
||||
3. Follow established coding standards
|
||||
4. Update documentation as needed
|
||||
|
||||
### Before Submitting
|
||||
1. Run full test suite
|
||||
2. Check code quality and formatting
|
||||
3. Update documentation if necessary
|
||||
4. Create clear pull request description
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### Error Handling
|
||||
- Use appropriate error handling mechanisms for the language
|
||||
- Provide meaningful error messages
|
||||
- Log errors appropriately for debugging
|
||||
- Handle edge cases gracefully
|
||||
- Don't expose sensitive information in error messages
|
||||
|
||||
### Performance Considerations
|
||||
- Profile code for performance bottlenecks
|
||||
- Optimize database queries and API calls
|
||||
- Use caching where appropriate
|
||||
- Consider memory usage and resource management
|
||||
- Monitor and measure performance metrics
|
||||
|
||||
### Code Reusability
|
||||
- Extract common functionality into reusable modules
|
||||
- Use dependency injection for better testability
|
||||
- Create utility functions for repeated operations
|
||||
- Design interfaces for extensibility
|
||||
- Follow DRY (Don't Repeat Yourself) principle
|
||||
|
||||
## Review Checklist
|
||||
|
||||
Before marking any task as complete:
|
||||
- [ ] Code follows established conventions
|
||||
- [ ] Tests are written and passing
|
||||
- [ ] Documentation is updated
|
||||
- [ ] Security considerations are addressed
|
||||
- [ ] Performance impact is considered
|
||||
- [ ] Code is reviewed for maintainability
|
||||
@@ -0,0 +1,96 @@
|
||||
# Common Claude Code Templates
|
||||
|
||||
This folder contains language-agnostic templates and configurations that can be used across different programming languages and project types.
|
||||
|
||||
## What's Included
|
||||
|
||||
### Core Files
|
||||
- `CLAUDE.md` - Base configuration for Claude Code with universal best practices
|
||||
- `.claude/commands/` - Custom commands for common development tasks
|
||||
|
||||
### Common Custom Commands
|
||||
- `git-workflow.md` - Git operations and workflow automation
|
||||
- `code-review.md` - Code review and quality assurance tasks
|
||||
- `project-setup.md` - Project initialization and configuration
|
||||
- `documentation.md` - Documentation generation and maintenance
|
||||
|
||||
## How to Use
|
||||
|
||||
### For New Projects
|
||||
Copy the entire `common/` folder contents to your project root:
|
||||
|
||||
```bash
|
||||
cp -r claude-code-templates/common/* your-project/
|
||||
```
|
||||
|
||||
### For Existing Projects
|
||||
Merge the relevant files with your existing Claude Code configuration:
|
||||
|
||||
```bash
|
||||
# Copy custom commands
|
||||
cp -r claude-code-templates/common/.claude/commands/* your-project/.claude/commands/
|
||||
|
||||
# Review and merge CLAUDE.md content
|
||||
cat claude-code-templates/common/CLAUDE.md >> your-project/CLAUDE.md
|
||||
```
|
||||
|
||||
## Customization
|
||||
|
||||
### CLAUDE.md
|
||||
The base CLAUDE.md file includes:
|
||||
- Universal development best practices
|
||||
- Common project patterns and conventions
|
||||
- Git workflow guidelines
|
||||
- Code quality standards
|
||||
|
||||
Customize it by:
|
||||
- Adding project-specific information
|
||||
- Modifying coding standards to match your team's preferences
|
||||
- Including technology-specific guidelines
|
||||
|
||||
### Custom Commands
|
||||
The included commands are designed to be generic and widely applicable. You can:
|
||||
- Modify existing commands to match your workflow
|
||||
- Add new commands for project-specific tasks
|
||||
- Remove commands that don't apply to your project
|
||||
|
||||
## Language-Specific Integration
|
||||
|
||||
This common template is designed to work alongside language-specific templates:
|
||||
|
||||
1. Start with the common template
|
||||
2. Add language-specific configurations from the appropriate folder
|
||||
3. Customize both to match your project needs
|
||||
|
||||
## Examples
|
||||
|
||||
### Multi-language Projects
|
||||
For projects using multiple programming languages:
|
||||
|
||||
```bash
|
||||
# Copy common base
|
||||
cp -r claude-code-templates/common/* your-project/
|
||||
|
||||
# Add language-specific configurations
|
||||
cp -r claude-code-templates/javascript-typescript/.claude/commands/* your-project/.claude/commands/
|
||||
cp -r claude-code-templates/python/.claude/commands/* your-project/.claude/commands/
|
||||
```
|
||||
|
||||
### Team Standardization
|
||||
Organizations can use this as a base template and customize it for their specific needs:
|
||||
|
||||
1. Fork this repository
|
||||
2. Modify the common templates to match your standards
|
||||
3. Distribute to development teams
|
||||
4. Keep synchronized with updates
|
||||
|
||||
## Best Practices
|
||||
|
||||
- **Start Simple**: Begin with the common template and add complexity as needed
|
||||
- **Document Changes**: Keep track of customizations in your project's documentation
|
||||
- **Regular Updates**: Periodically review and update your Claude Code configuration
|
||||
- **Team Alignment**: Ensure all team members understand the custom commands and workflows
|
||||
|
||||
## Contributing
|
||||
|
||||
If you create useful generic commands or improvements to the common template, consider contributing them back to this repository to help other developers.
|
||||
@@ -0,0 +1,78 @@
|
||||
{
|
||||
"mcpServers": {
|
||||
"go-sdk": {
|
||||
"name": "Go SDK",
|
||||
"description": "Official SDK maintained with Google for Go MCP development",
|
||||
"command": "go-sdk-server",
|
||||
"args": [],
|
||||
"env": {}
|
||||
},
|
||||
"language-server": {
|
||||
"name": "MCP Language Server",
|
||||
"description": "Semantic tools for Go: definitions, references, diagnostics",
|
||||
"command": "mcp-language-server",
|
||||
"args": [],
|
||||
"env": {}
|
||||
},
|
||||
"gin": {
|
||||
"name": "Gin MCP",
|
||||
"description": "Expose Gin APIs automatically as MCP tools",
|
||||
"command": "gin-mcp",
|
||||
"args": [],
|
||||
"env": {}
|
||||
},
|
||||
"mysql": {
|
||||
"name": "Go MySQL MCP",
|
||||
"description": "Easy-to-use MySQL MCP server built in Go",
|
||||
"command": "go-mcp-mysql",
|
||||
"args": [],
|
||||
"env": {
|
||||
"MYSQL_DSN": "user:pass@tcp(host)/db"
|
||||
}
|
||||
},
|
||||
"archer": {
|
||||
"name": "Go Archer",
|
||||
"description": "Visual dependency analysis for Go packages",
|
||||
"command": "go-archer",
|
||||
"args": [],
|
||||
"env": {}
|
||||
},
|
||||
"memory-bank": {
|
||||
"name": "Memory Bank MCP",
|
||||
"description": "Centralized memory system for AI agents",
|
||||
"command": "server-memory",
|
||||
"args": [],
|
||||
"env": {}
|
||||
},
|
||||
"sequential-thinking": {
|
||||
"name": "Sequential Thinking MCP",
|
||||
"description": "Helps LLMs decompose complex tasks into logical steps",
|
||||
"command": "code-reasoning",
|
||||
"args": [],
|
||||
"env": {}
|
||||
},
|
||||
"brave-search": {
|
||||
"name": "Brave Search MCP",
|
||||
"description": "Privacy-focused web search tool",
|
||||
"command": "server-brave-search",
|
||||
"args": [],
|
||||
"env": {}
|
||||
},
|
||||
"google-maps": {
|
||||
"name": "Google Maps MCP",
|
||||
"description": "Integrates Google Maps for geolocation and directions",
|
||||
"command": "server-google-maps",
|
||||
"args": [],
|
||||
"env": {
|
||||
"GOOGLE_MAPS_API_KEY": "..."
|
||||
}
|
||||
},
|
||||
"deep-graph": {
|
||||
"name": "Deep Graph MCP (Code Graph)",
|
||||
"description": "Transforms source code into semantic graphs via DeepGraph",
|
||||
"command": "mcp-code-graph",
|
||||
"args": [],
|
||||
"env": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
# Go Claude Code Templates
|
||||
|
||||
## Coming Soon! 🚧
|
||||
|
||||
We're actively working on creating comprehensive Claude Code templates for Go development.
|
||||
|
||||
### What to Expect
|
||||
- Best practices for Go project structure
|
||||
- Integration with popular Go tools and frameworks
|
||||
- Workflow optimizations for Go development
|
||||
- Testing and benchmarking configurations
|
||||
- Deployment and build automation
|
||||
|
||||
### Meanwhile...
|
||||
You can use the [common templates](../common/README.md) as a starting point for your Go projects. The universal guidelines and git workflows will work well with Go development.
|
||||
|
||||
### Stay Updated
|
||||
⭐ Star this repository to get notified when the Go templates are released!
|
||||
|
||||
### Contributing
|
||||
Interested in helping build these templates? We welcome contributions! Please check the main repository's contribution guidelines and feel free to open an issue or pull request.
|
||||
|
||||
---
|
||||
|
||||
*Expected release: Coming soon*
|
||||
@@ -0,0 +1,51 @@
|
||||
# API Endpoint Generator
|
||||
|
||||
Generate a complete API endpoint for $ARGUMENTS following project conventions.
|
||||
|
||||
## Task
|
||||
|
||||
I'll analyze the project structure and create a new API endpoint with:
|
||||
|
||||
1. Route definition
|
||||
2. Controller/handler function
|
||||
3. Input validation
|
||||
4. Service layer logic (if applicable)
|
||||
5. Data access layer (if applicable)
|
||||
6. Unit tests
|
||||
7. Documentation
|
||||
|
||||
## Process
|
||||
|
||||
I'll follow these steps:
|
||||
|
||||
1. Examine the project structure to understand the architecture pattern
|
||||
2. Identify existing patterns for routes, controllers, and validation
|
||||
3. Create all necessary files following project conventions
|
||||
4. Implement the endpoint with proper error handling
|
||||
5. Add appropriate tests
|
||||
6. Document the new endpoint
|
||||
|
||||
## Best Practices
|
||||
|
||||
I'll ensure the implementation includes:
|
||||
|
||||
- Strong typing with TypeScript
|
||||
- Comprehensive error handling
|
||||
- Input validation
|
||||
- Security considerations (authentication/authorization)
|
||||
- Proper logging
|
||||
- Performance considerations
|
||||
- Test coverage
|
||||
|
||||
## Adaptability
|
||||
|
||||
I'll adapt to various API architectures:
|
||||
|
||||
- Express/Koa/Fastify REST APIs
|
||||
- GraphQL resolvers
|
||||
- Next.js/Nuxt.js API routes
|
||||
- Serverless functions
|
||||
- tRPC endpoints
|
||||
- NestJS controllers
|
||||
|
||||
I'll examine your project first to determine which pattern to follow.
|
||||
@@ -0,0 +1,52 @@
|
||||
# Debug Assistant
|
||||
|
||||
Help me debug the issue with $ARGUMENTS in this project.
|
||||
|
||||
## Task
|
||||
|
||||
I'll help you identify and fix the problem by:
|
||||
|
||||
1. Understanding the issue description
|
||||
2. Analyzing relevant code
|
||||
3. Identifying potential causes
|
||||
4. Suggesting and implementing fixes
|
||||
5. Verifying the solution works
|
||||
|
||||
## Process
|
||||
|
||||
I'll follow these steps:
|
||||
|
||||
1. Examine error messages, logs, or unexpected behaviors
|
||||
2. Locate relevant files and code sections
|
||||
3. Analyze the code flow and potential failure points
|
||||
4. Identify common JavaScript/TypeScript pitfalls that might apply
|
||||
5. Suggest specific fixes with explanations
|
||||
6. Help implement and test the solution
|
||||
|
||||
## Debugging Techniques
|
||||
|
||||
I'll apply appropriate debugging techniques such as:
|
||||
|
||||
- Static code analysis to find syntax or type errors
|
||||
- Runtime error analysis from logs or stack traces
|
||||
- Control flow tracing to understand execution paths
|
||||
- State inspection to identify incorrect values
|
||||
- Dependency analysis to find version conflicts
|
||||
- Network request inspection for API issues
|
||||
- Browser console analysis for frontend problems
|
||||
- Database query inspection for data issues
|
||||
|
||||
## Common Issues I Can Help With
|
||||
|
||||
- Type errors and null/undefined issues
|
||||
- Asynchronous code problems (Promises, async/await)
|
||||
- React/Vue/Angular component lifecycle issues
|
||||
- API integration problems
|
||||
- State management bugs
|
||||
- Performance bottlenecks
|
||||
- Memory leaks
|
||||
- Build/compilation errors
|
||||
- Testing failures
|
||||
- Environment configuration issues
|
||||
|
||||
I'll adapt my approach based on your specific project structure, frameworks, and the nature of the problem.
|
||||
@@ -0,0 +1,48 @@
|
||||
# Lint Assistant
|
||||
|
||||
Analyze and fix linting issues in $ARGUMENTS following project conventions.
|
||||
|
||||
## Task
|
||||
|
||||
I'll help you identify and fix code style and quality issues by:
|
||||
|
||||
1. Running appropriate linters for the project
|
||||
2. Analyzing linting errors and warnings
|
||||
3. Fixing issues automatically when possible
|
||||
4. Explaining complex issues that require manual intervention
|
||||
5. Ensuring code follows project style guidelines
|
||||
|
||||
## Process
|
||||
|
||||
I'll follow these steps:
|
||||
|
||||
1. Identify the linting tools used in the project (ESLint, Prettier, TSLint, etc.)
|
||||
2. Run the appropriate linting commands
|
||||
3. Parse and categorize the results
|
||||
4. Apply automatic fixes for common issues
|
||||
5. Provide explanations and suggestions for more complex problems
|
||||
6. Verify fixes don't introduce new issues
|
||||
|
||||
## Common Linting Issues I Can Fix
|
||||
|
||||
- Code style inconsistencies (spacing, indentation, quotes, etc.)
|
||||
- Unused variables and imports
|
||||
- Missing type annotations in TypeScript
|
||||
- Accessibility (a11y) issues in UI components
|
||||
- Potential bugs flagged by static analysis
|
||||
- Performance issues in React/Vue components
|
||||
- Security vulnerabilities detected by linters
|
||||
- Deprecated API usage
|
||||
- Import ordering problems
|
||||
- Missing documentation
|
||||
|
||||
## Linting Tools I Can Work With
|
||||
|
||||
- ESLint (with various plugins and configs)
|
||||
- Prettier
|
||||
- TSLint (legacy)
|
||||
- stylelint (for CSS/SCSS)
|
||||
- commitlint (for commit messages)
|
||||
- Custom lint rules specific to your project
|
||||
|
||||
I'll adapt my approach based on your project's specific linting configuration and style guide requirements.
|
||||
@@ -0,0 +1,48 @@
|
||||
# NPM Scripts Assistant
|
||||
|
||||
Help me with NPM scripts: $ARGUMENTS
|
||||
|
||||
## Task
|
||||
|
||||
I'll help you work with package.json scripts by:
|
||||
|
||||
1. Analyzing existing npm scripts in your project
|
||||
2. Creating new scripts or modifying existing ones
|
||||
3. Explaining what specific scripts do
|
||||
4. Suggesting improvements or optimizations
|
||||
5. Troubleshooting script execution issues
|
||||
|
||||
## Process
|
||||
|
||||
I'll follow these steps:
|
||||
|
||||
1. Examine your package.json file to understand current scripts
|
||||
2. Analyze dependencies and devDependencies for available tools
|
||||
3. Identify common patterns and conventions in your scripts
|
||||
4. Implement requested changes or create new scripts
|
||||
5. Provide explanations of how the scripts work
|
||||
6. Test scripts when possible to verify functionality
|
||||
|
||||
## Common Script Types I Can Help With
|
||||
|
||||
- Build processes (webpack, rollup, esbuild, etc.)
|
||||
- Development servers and hot reloading
|
||||
- Testing (unit, integration, e2e)
|
||||
- Linting and code formatting
|
||||
- Type checking
|
||||
- Deployment and CI/CD
|
||||
- Database migrations
|
||||
- Code generation
|
||||
- Environment setup
|
||||
- Pre/post hooks for git operations
|
||||
|
||||
## Script Optimization Techniques
|
||||
|
||||
- Parallelizing tasks for faster execution
|
||||
- Adding cross-platform compatibility
|
||||
- Improving error reporting and logging
|
||||
- Implementing watch modes for development
|
||||
- Creating composite scripts for common workflows
|
||||
- Adding appropriate exit codes for CI/CD pipelines
|
||||
|
||||
I'll adapt my approach based on your project's specific needs, dependencies, and build tools.
|
||||
@@ -0,0 +1,55 @@
|
||||
# Code Refactoring Assistant
|
||||
|
||||
Refactor $ARGUMENTS following modern JavaScript/TypeScript best practices.
|
||||
|
||||
## Task
|
||||
|
||||
I'll help you refactor code by:
|
||||
|
||||
1. Analyzing the current implementation
|
||||
2. Identifying improvement opportunities
|
||||
3. Applying modern patterns and practices
|
||||
4. Maintaining existing functionality
|
||||
5. Ensuring type safety and test coverage
|
||||
6. Documenting the changes made
|
||||
|
||||
## Process
|
||||
|
||||
I'll follow these steps:
|
||||
|
||||
1. Examine the code to understand its purpose and structure
|
||||
2. Identify code smells, anti-patterns, or outdated approaches
|
||||
3. Plan the refactoring strategy with clear goals
|
||||
4. Implement changes incrementally while maintaining behavior
|
||||
5. Verify refactored code with tests
|
||||
6. Document improvements and benefits
|
||||
|
||||
## Refactoring Techniques
|
||||
|
||||
I can apply various refactoring techniques:
|
||||
|
||||
- Converting to modern JavaScript/TypeScript features
|
||||
- Improving type definitions and type safety
|
||||
- Extracting reusable functions and components
|
||||
- Applying design patterns appropriately
|
||||
- Converting callbacks to Promises or async/await
|
||||
- Simplifying complex conditionals and loops
|
||||
- Removing duplicate code
|
||||
- Improving naming and readability
|
||||
- Optimizing performance
|
||||
- Enhancing error handling
|
||||
|
||||
## Modern Practices I Can Apply
|
||||
|
||||
- ES modules and import/export syntax
|
||||
- Optional chaining and nullish coalescing
|
||||
- Array and object destructuring
|
||||
- Spread and rest operators
|
||||
- Template literals
|
||||
- Arrow functions
|
||||
- Class fields and private methods
|
||||
- TypeScript utility types
|
||||
- Functional programming patterns
|
||||
- React hooks (for React components)
|
||||
|
||||
I'll ensure the refactored code maintains compatibility with your project's requirements while improving quality and maintainability.
|
||||
@@ -0,0 +1,61 @@
|
||||
# Test Assistant
|
||||
|
||||
Help with tests for $ARGUMENTS following project conventions and testing best practices.
|
||||
|
||||
## Task
|
||||
|
||||
I'll help you with testing by:
|
||||
|
||||
1. Creating comprehensive test suites for your code
|
||||
2. Implementing different types of tests (unit, integration, e2e)
|
||||
3. Mocking dependencies and external services
|
||||
4. Improving test coverage for existing code
|
||||
5. Troubleshooting failing tests
|
||||
6. Setting up testing infrastructure
|
||||
|
||||
## Process
|
||||
|
||||
I'll follow these steps:
|
||||
|
||||
1. Examine your project to understand testing frameworks and patterns
|
||||
2. Analyze the code to be tested to understand its functionality
|
||||
3. Identify appropriate testing strategies and edge cases
|
||||
4. Implement tests with proper structure and assertions
|
||||
5. Ensure tests are maintainable and follow best practices
|
||||
6. Run tests to verify they pass and provide adequate coverage
|
||||
|
||||
## Testing Frameworks I Can Work With
|
||||
|
||||
- Jest, Vitest, Mocha, Jasmine for JavaScript/TypeScript
|
||||
- React Testing Library, Enzyme for React components
|
||||
- Cypress, Playwright, Puppeteer for E2E testing
|
||||
- Supertest, Pactum for API testing
|
||||
- Storybook for component testing
|
||||
- Testing-library family for various frameworks
|
||||
|
||||
## Testing Techniques
|
||||
|
||||
I can implement various testing approaches:
|
||||
|
||||
- TDD (Test-Driven Development)
|
||||
- BDD (Behavior-Driven Development)
|
||||
- Snapshot testing
|
||||
- Property-based testing
|
||||
- Parameterized tests
|
||||
- Contract testing
|
||||
- Visual regression testing
|
||||
- Performance testing
|
||||
|
||||
## Mocking Strategies
|
||||
|
||||
I can help with different mocking approaches:
|
||||
|
||||
- Function mocks and spies
|
||||
- Module mocks
|
||||
- HTTP request mocking
|
||||
- Browser API mocking
|
||||
- Timer mocking
|
||||
- Database mocking
|
||||
- Service worker mocking
|
||||
|
||||
I'll adapt to your project's specific testing frameworks, patterns, and conventions to ensure consistency with your existing codebase.
|
||||
@@ -0,0 +1,51 @@
|
||||
# TypeScript Migration Assistant
|
||||
|
||||
Migrate $ARGUMENTS from JavaScript to TypeScript with proper typing and modern practices.
|
||||
|
||||
## Task
|
||||
|
||||
I'll help you migrate JavaScript code to TypeScript by:
|
||||
|
||||
1. Converting JavaScript files to TypeScript (.js/.jsx to .ts/.tsx)
|
||||
2. Adding appropriate type definitions and interfaces
|
||||
3. Configuring TypeScript settings for your project
|
||||
4. Resolving type errors and compatibility issues
|
||||
5. Implementing modern TypeScript patterns and features
|
||||
6. Ensuring backward compatibility with existing code
|
||||
|
||||
## Process
|
||||
|
||||
I'll follow these steps:
|
||||
|
||||
1. Examine your project structure and dependencies
|
||||
2. Analyze the JavaScript code to understand its functionality
|
||||
3. Create or update tsconfig.json with appropriate settings
|
||||
4. Convert files to TypeScript with proper type annotations
|
||||
5. Add necessary type definitions for libraries
|
||||
6. Fix type errors and implement type safety
|
||||
7. Refactor code to leverage TypeScript features
|
||||
|
||||
## TypeScript Features I Can Implement
|
||||
|
||||
- Interfaces and type aliases for complex data structures
|
||||
- Generics for reusable, type-safe components and functions
|
||||
- Union and intersection types for flexible typing
|
||||
- Enums for related constants
|
||||
- Utility types (Partial, Pick, Omit, etc.)
|
||||
- Type guards and type narrowing
|
||||
- Mapped and conditional types
|
||||
- Function overloading
|
||||
- Readonly properties and immutability
|
||||
- Module augmentation for extending third-party types
|
||||
|
||||
## Migration Strategies
|
||||
|
||||
I can apply different migration approaches based on your needs:
|
||||
|
||||
- Gradual migration with allowJs and incremental adoption
|
||||
- File-by-file conversion while maintaining functionality
|
||||
- Comprehensive migration with full type safety
|
||||
- Hybrid approach with .d.ts files for complex modules
|
||||
- Integration with existing type definitions (@types packages)
|
||||
|
||||
I'll adapt to your project's specific requirements and ensure a smooth transition to TypeScript while maintaining existing functionality.
|
||||
@@ -0,0 +1,142 @@
|
||||
{
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"Bash",
|
||||
"Edit",
|
||||
"MultiEdit",
|
||||
"Write",
|
||||
"Bash(npm:*)",
|
||||
"Bash(yarn:*)",
|
||||
"Bash(npx:*)",
|
||||
"Bash(node:*)",
|
||||
"Bash(git:*)",
|
||||
"Bash(eslint:*)",
|
||||
"Bash(prettier:*)",
|
||||
"Bash(tsc:*)",
|
||||
"Bash(jest:*)",
|
||||
"Bash(vitest:*)",
|
||||
"Bash(webpack:*)",
|
||||
"Bash(vite:*)"
|
||||
],
|
||||
"deny": [
|
||||
"Bash(curl:*)",
|
||||
"Bash(wget:*)",
|
||||
"Bash(rm -rf:*)"
|
||||
],
|
||||
"defaultMode": "allowEdits"
|
||||
},
|
||||
"env": {
|
||||
"BASH_DEFAULT_TIMEOUT_MS": "60000",
|
||||
"BASH_MAX_OUTPUT_LENGTH": "20000",
|
||||
"CLAUDE_BASH_MAINTAIN_PROJECT_WORKING_DIR": "1",
|
||||
"NODE_ENV": "development"
|
||||
},
|
||||
"includeCoAuthoredBy": true,
|
||||
"cleanupPeriodDays": 30,
|
||||
"hooks": {
|
||||
"PreToolUse": [
|
||||
{
|
||||
"matcher": "Bash",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "jq -r '\"\\(.tool_input.command) - \\(.tool_input.description // \"No description\")\"' >> ~/.claude/bash-command-log.txt"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"matcher": "Write",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "FILE=$(echo $STDIN_JSON | jq -r '.tool_input.file_path // \"\"); CONTENT=$(echo $STDIN_JSON | jq -r '.tool_input.content // \"\"); if [[ \"$FILE\" =~ \\.(js|jsx|ts|tsx)$ ]] && echo \"$CONTENT\" | grep -q 'console\\.log'; then echo 'Warning: console.log statements should be removed before committing' >&2; exit 2; fi"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"matcher": "Write",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "FILE=$(echo $STDIN_JSON | jq -r '.tool_input.file_path // \"\"'); if [[ \"$FILE\" == \"package.json\" ]]; then echo 'Checking for vulnerable dependencies...'; npm audit --audit-level=moderate; fi",
|
||||
"timeout": 60
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"PostToolUse": [
|
||||
{
|
||||
"matcher": "Write|Edit|MultiEdit",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "FILE=$(echo $STDIN_JSON | jq -r '.tool_input.file_path // \"\"'); if [[ \"$FILE\" =~ \\.(js|jsx|ts|tsx)$ ]]; then npx prettier --write \"$FILE\"; fi",
|
||||
"timeout": 30
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"matcher": "Write|Edit|MultiEdit",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "FILE=$(echo $STDIN_JSON | jq -r '.tool_input.file_path // \"\"'); if [[ \"$FILE\" =~ \\.(ts|tsx)$ ]]; then RESULT=$(npx tsc --noEmit 2>&1); if [ $? -ne 0 ]; then echo \"TypeScript errors found: $RESULT\" >&2; exit 2; fi; fi",
|
||||
"timeout": 30
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"matcher": "Write|Edit|MultiEdit",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "FILE=$(echo $STDIN_JSON | jq -r '.tool_input.file_path // \"\"'); if [[ \"$FILE\" =~ \\.(js|jsx|ts|tsx)$ ]] && grep -q 'import \\* from' \"$FILE\"; then echo 'Warning: Avoid wildcard imports for better tree-shaking' >&2; exit 2; fi"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"matcher": "Write|Edit|MultiEdit",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "FILE=$(echo $STDIN_JSON | jq -r '.tool_input.file_path // \"\"'); if [[ \"$FILE\" =~ \\.(js|jsx|ts|tsx)$ && \"$FILE\" != *\".test.\"* && \"$FILE\" != *\".spec.\"* ]]; then DIR=$(dirname \"$FILE\"); BASENAME=$(basename \"$FILE\" | sed -E 's/\\.(js|jsx|ts|tsx)$//'); for TEST_FILE in \"$DIR/$BASENAME.test.\"* \"$DIR/$BASENAME.spec.\"*; do if [ -f \"$TEST_FILE\" ]; then echo \"Running tests for $TEST_FILE...\"; if command -v jest >/dev/null 2>&1; then npx jest \"$TEST_FILE\" --passWithNoTests; elif command -v vitest >/dev/null 2>&1; then npx vitest run \"$TEST_FILE\"; fi; break; fi; done; fi",
|
||||
"timeout": 60
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"Notification": [
|
||||
{
|
||||
"matcher": "",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "echo \"Claude Code notification: $(date)\" >> ~/.claude/notifications.log"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"Stop": [
|
||||
{
|
||||
"matcher": "",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "if [[ -f package.json && $(git status --porcelain | grep -E '\\.js$|\\.jsx$|\\.ts$|\\.tsx$') ]]; then echo 'Running linter on changed files...'; npx eslint $(git diff --name-only --diff-filter=ACMR | grep -E '\\.js$|\\.jsx$|\\.ts$|\\.tsx$'); fi",
|
||||
"timeout": 60
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"matcher": "",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "if [[ -f package.json && $(git status --porcelain | grep -E '\\.js$|\\.jsx$|\\.ts$|\\.tsx$') ]]; then echo 'Analyzing bundle size impact...'; if command -v bundlesize >/dev/null 2>&1; then npx bundlesize; elif npm list webpack-bundle-analyzer >/dev/null 2>&1; then echo 'Use: npx webpack-bundle-analyzer build/static/js/*.js'; else echo 'No bundle analysis tool found'; fi; fi",
|
||||
"timeout": 60
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
{
|
||||
"mcpServers": {
|
||||
"typescript-sdk": {
|
||||
"name": "TypeScript SDK",
|
||||
"description": "Official Anthropic SDK for building MCP servers and clients in JS/TS",
|
||||
"command": "node",
|
||||
"args": ["path/to/ts-sdk-server.js"],
|
||||
"env": {}
|
||||
},
|
||||
"github": {
|
||||
"name": "GitHub MCP",
|
||||
"description": "Integration with GitHub API for managing repos, issues, and PRs",
|
||||
"command": "node",
|
||||
"args": ["path/to/server-github"],
|
||||
"env": {
|
||||
"GITHUB_TOKEN": "..."
|
||||
}
|
||||
},
|
||||
"puppeteer": {
|
||||
"name": "Puppeteer MCP",
|
||||
"description": "Browser automation using Google Puppeteer",
|
||||
"command": "node",
|
||||
"args": ["path/to/server-puppeteer"],
|
||||
"env": {}
|
||||
},
|
||||
"slack": {
|
||||
"name": "Slack MCP",
|
||||
"description": "Access to real-time Slack conversations and workflows",
|
||||
"command": "node",
|
||||
"args": ["path/to/server-slack"],
|
||||
"env": {
|
||||
"SLACK_TOKEN": "..."
|
||||
}
|
||||
},
|
||||
"filesystem": {
|
||||
"name": "File System MCP",
|
||||
"description": "Local file management; compatible with any language",
|
||||
"command": "node",
|
||||
"args": ["path/to/server-filesystem"],
|
||||
"env": {}
|
||||
},
|
||||
"memory-bank": {
|
||||
"name": "Memory Bank MCP",
|
||||
"description": "Centralized memory system for AI agents",
|
||||
"command": "server-memory",
|
||||
"args": [],
|
||||
"env": {}
|
||||
},
|
||||
"sequential-thinking": {
|
||||
"name": "Sequential Thinking MCP",
|
||||
"description": "Helps LLMs decompose complex tasks into logical steps",
|
||||
"command": "code-reasoning",
|
||||
"args": [],
|
||||
"env": {}
|
||||
},
|
||||
"brave-search": {
|
||||
"name": "Brave Search MCP",
|
||||
"description": "Privacy-focused web search tool",
|
||||
"command": "server-brave-search",
|
||||
"args": [],
|
||||
"env": {}
|
||||
},
|
||||
"google-maps": {
|
||||
"name": "Google Maps MCP",
|
||||
"description": "Integrates Google Maps for geolocation and directions",
|
||||
"command": "server-google-maps",
|
||||
"args": [],
|
||||
"env": {
|
||||
"GOOGLE_MAPS_API_KEY": "..."
|
||||
}
|
||||
},
|
||||
"deep-graph": {
|
||||
"name": "Deep Graph MCP (Code Graph)",
|
||||
"description": "Transforms source code into semantic graphs via DeepGraph",
|
||||
"command": "mcp-code-graph",
|
||||
"args": [],
|
||||
"env": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,185 @@
|
||||
# CLAUDE.md
|
||||
|
||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||
|
||||
## Project Overview
|
||||
|
||||
This is a JavaScript/TypeScript project optimized for modern web development. The project uses industry-standard tools and follows best practices for scalable application development.
|
||||
|
||||
## Development Commands
|
||||
|
||||
### Package Management
|
||||
- `npm install` or `yarn install` - Install dependencies
|
||||
- `npm ci` or `yarn install --frozen-lockfile` - Install dependencies for CI/CD
|
||||
- `npm update` or `yarn upgrade` - Update dependencies
|
||||
|
||||
### Build Commands
|
||||
- `npm run build` - Build the project for production
|
||||
- `npm run dev` or `npm start` - Start development server
|
||||
- `npm run preview` - Preview production build locally
|
||||
|
||||
### Testing Commands
|
||||
- `npm test` or `npm run test` - Run all tests
|
||||
- `npm run test:watch` - Run tests in watch mode
|
||||
- `npm run test:coverage` - Run tests with coverage report
|
||||
- `npm run test:unit` - Run unit tests only
|
||||
- `npm run test:integration` - Run integration tests only
|
||||
- `npm run test:e2e` - Run end-to-end tests
|
||||
|
||||
### Code Quality Commands
|
||||
- `npm run lint` - Run ESLint for code linting
|
||||
- `npm run lint:fix` - Run ESLint with auto-fix
|
||||
- `npm run format` - Format code with Prettier
|
||||
- `npm run format:check` - Check code formatting
|
||||
- `npm run typecheck` - Run TypeScript type checking
|
||||
|
||||
### Development Tools
|
||||
- `npm run storybook` - Start Storybook (if available)
|
||||
- `npm run analyze` - Analyze bundle size
|
||||
- `npm run clean` - Clean build artifacts
|
||||
|
||||
## Technology Stack
|
||||
|
||||
### Core Technologies
|
||||
- **JavaScript/TypeScript** - Primary programming languages
|
||||
- **Node.js** - Runtime environment
|
||||
- **npm/yarn** - Package management
|
||||
|
||||
### Common Frameworks
|
||||
- **React** - UI library with hooks and functional components
|
||||
- **Vue.js** - Progressive framework for building user interfaces
|
||||
- **Angular** - Full-featured framework for web applications
|
||||
- **Express.js** - Web application framework for Node.js
|
||||
- **Next.js** - React framework with SSR/SSG capabilities
|
||||
|
||||
### Build Tools
|
||||
- **Vite** - Fast build tool and development server
|
||||
- **Webpack** - Module bundler
|
||||
- **Rollup** - Module bundler for libraries
|
||||
- **esbuild** - Extremely fast JavaScript bundler
|
||||
|
||||
### Testing Framework
|
||||
- **Jest** - JavaScript testing framework
|
||||
- **Vitest** - Fast unit test framework
|
||||
- **Testing Library** - Simple and complete testing utilities
|
||||
- **Cypress** - End-to-end testing framework
|
||||
- **Playwright** - Cross-browser testing
|
||||
|
||||
### Code Quality Tools
|
||||
- **ESLint** - JavaScript/TypeScript linter
|
||||
- **Prettier** - Code formatter
|
||||
- **TypeScript** - Static type checking
|
||||
- **Husky** - Git hooks
|
||||
|
||||
## Project Structure Guidelines
|
||||
|
||||
### File Organization
|
||||
```
|
||||
src/
|
||||
├── components/ # Reusable UI components
|
||||
├── pages/ # Page components or routes
|
||||
├── hooks/ # Custom React hooks
|
||||
├── utils/ # Utility functions
|
||||
├── services/ # API calls and external services
|
||||
├── types/ # TypeScript type definitions
|
||||
├── constants/ # Application constants
|
||||
├── styles/ # Global styles and themes
|
||||
└── tests/ # Test files
|
||||
```
|
||||
|
||||
### Naming Conventions
|
||||
- **Files**: Use kebab-case for file names (`user-profile.component.ts`)
|
||||
- **Components**: Use PascalCase for component names (`UserProfile`)
|
||||
- **Functions**: Use camelCase for function names (`getUserData`)
|
||||
- **Constants**: Use UPPER_SNAKE_CASE for constants (`API_BASE_URL`)
|
||||
- **Types/Interfaces**: Use PascalCase with descriptive names (`UserData`, `ApiResponse`)
|
||||
|
||||
## TypeScript Guidelines
|
||||
|
||||
### Type Safety
|
||||
- Enable strict mode in `tsconfig.json`
|
||||
- Use explicit types for function parameters and return values
|
||||
- Prefer interfaces over types for object shapes
|
||||
- Use union types for multiple possible values
|
||||
- Avoid `any` type - use `unknown` when type is truly unknown
|
||||
|
||||
### Best Practices
|
||||
- Use type guards for runtime type checking
|
||||
- Leverage utility types (`Partial`, `Pick`, `Omit`, etc.)
|
||||
- Create custom types for domain-specific data
|
||||
- Use enums for finite sets of values
|
||||
- Document complex types with JSDoc comments
|
||||
|
||||
## Code Quality Standards
|
||||
|
||||
### ESLint Configuration
|
||||
- Use recommended ESLint rules for JavaScript/TypeScript
|
||||
- Enable React-specific rules if using React
|
||||
- Configure import/export rules for consistent module usage
|
||||
- Set up accessibility rules for inclusive development
|
||||
|
||||
### Prettier Configuration
|
||||
- Use consistent indentation (2 spaces recommended)
|
||||
- Set maximum line length (80-100 characters)
|
||||
- Use single quotes for strings
|
||||
- Add trailing commas for better git diffs
|
||||
|
||||
### Testing Standards
|
||||
- Aim for 80%+ test coverage
|
||||
- Write unit tests for utilities and business logic
|
||||
- Use integration tests for component interactions
|
||||
- Implement e2e tests for critical user flows
|
||||
- Follow AAA pattern (Arrange, Act, Assert)
|
||||
|
||||
## Performance Optimization
|
||||
|
||||
### Bundle Optimization
|
||||
- Use code splitting for large applications
|
||||
- Implement lazy loading for routes and components
|
||||
- Optimize images and assets
|
||||
- Use tree shaking to eliminate dead code
|
||||
- Analyze bundle size regularly
|
||||
|
||||
### Runtime Performance
|
||||
- Implement proper memoization (React.memo, useMemo, useCallback)
|
||||
- Use virtualization for large lists
|
||||
- Optimize re-renders in React applications
|
||||
- Implement proper error boundaries
|
||||
- Use web workers for heavy computations
|
||||
|
||||
## Security Guidelines
|
||||
|
||||
### Dependencies
|
||||
- Regularly audit dependencies with `npm audit`
|
||||
- Keep dependencies updated
|
||||
- Use lock files (`package-lock.json`, `yarn.lock`)
|
||||
- Avoid dependencies with known vulnerabilities
|
||||
|
||||
### Code Security
|
||||
- Sanitize user inputs
|
||||
- Use HTTPS for API calls
|
||||
- Implement proper authentication and authorization
|
||||
- Store sensitive data securely (environment variables)
|
||||
- Use Content Security Policy (CSP) headers
|
||||
|
||||
## Development Workflow
|
||||
|
||||
### Before Starting
|
||||
1. Check Node.js version compatibility
|
||||
2. Install dependencies with `npm install`
|
||||
3. Copy environment variables from `.env.example`
|
||||
4. Run type checking with `npm run typecheck`
|
||||
|
||||
### During Development
|
||||
1. Use TypeScript for type safety
|
||||
2. Run linter frequently to catch issues early
|
||||
3. Write tests for new features
|
||||
4. Use meaningful commit messages
|
||||
5. Review code changes before committing
|
||||
|
||||
### Before Committing
|
||||
1. Run full test suite: `npm test`
|
||||
2. Check linting: `npm run lint`
|
||||
3. Verify formatting: `npm run format:check`
|
||||
4. Run type checking: `npm run typecheck`
|
||||
5. Test production build: `npm run build`
|
||||
@@ -0,0 +1,259 @@
|
||||
# JavaScript/TypeScript Templates
|
||||
|
||||
**Claude Code configuration template optimized for modern JavaScript and TypeScript development**
|
||||
|
||||
This folder contains a comprehensive Claude Code template specifically designed for JavaScript and TypeScript projects, supporting popular frameworks like React, Vue.js, Angular, and Node.js.
|
||||
|
||||
## 📁 What's in This Folder
|
||||
|
||||
This template provides the foundation for JavaScript/TypeScript development with Claude Code:
|
||||
|
||||
### 📄 Files Included
|
||||
- **`CLAUDE.md`** - Complete JavaScript/TypeScript development guidance for Claude Code
|
||||
- **`README.md`** - This documentation file
|
||||
|
||||
### 🎯 Template Features
|
||||
When you use this template with the installer, it automatically creates:
|
||||
- **`.claude/settings.json`** - Optimized settings for JS/TS projects
|
||||
- **`.claude/commands/`** - Ready-to-use commands for common tasks
|
||||
|
||||
## 🚀 How to Use This Template
|
||||
|
||||
### Option 1: Automated Installation (Recommended)
|
||||
Use the CLI installer to automatically set up this template in your project:
|
||||
|
||||
```bash
|
||||
cd your-javascript-project
|
||||
npx claude-code-templates --language javascript-typescript
|
||||
```
|
||||
|
||||
The installer will:
|
||||
- Copy the `CLAUDE.md` file to your project
|
||||
- Auto-detect your framework (React, Vue, Node.js, etc.)
|
||||
- Create appropriate `.claude/` configuration files
|
||||
- Set up framework-specific commands
|
||||
- Configure development workflows
|
||||
|
||||
### Option 2: Manual Installation
|
||||
Copy the template manually for more control:
|
||||
|
||||
```bash
|
||||
# Clone the repository
|
||||
git clone https://github.com/davila7/claude-code-templates.git
|
||||
|
||||
# Copy the JavaScript/TypeScript template
|
||||
cp claude-code-templates/javascript-typescript/CLAUDE.md your-project/
|
||||
|
||||
# Then use the CLI to complete the setup
|
||||
cd your-project
|
||||
npx claude-code-templates --language javascript-typescript
|
||||
```
|
||||
|
||||
## 🎨 Framework Support
|
||||
|
||||
This template automatically configures Claude Code for:
|
||||
|
||||
### Frontend Frameworks
|
||||
- **React** - Components, hooks, JSX, testing with React Testing Library
|
||||
- **Vue.js** - Composition API, single-file components, state management
|
||||
- **Angular** - TypeScript-first development, RxJS patterns, CLI integration
|
||||
- **Svelte** - Compile-time optimizations, modern JavaScript patterns
|
||||
|
||||
### Backend Frameworks
|
||||
- **Express.js** - RESTful APIs, middleware, error handling
|
||||
- **Fastify** - High-performance Node.js applications
|
||||
- **NestJS** - Enterprise-grade TypeScript framework
|
||||
- **Next.js** - Full-stack React applications with SSR/SSG
|
||||
|
||||
### Build Tools & Testing
|
||||
- **Vite, Webpack, esbuild** - Modern build tool configurations
|
||||
- **Jest, Vitest, Cypress** - Testing framework optimization
|
||||
- **ESLint, Prettier, TypeScript** - Code quality and formatting
|
||||
|
||||
## 🛠️ Commands Created by the Template
|
||||
|
||||
When installed, this template provides commands for:
|
||||
|
||||
### 🧪 Testing & Quality
|
||||
- **`/test`** - Run tests with Jest, Vitest, or other frameworks
|
||||
- **`/lint`** - ESLint with auto-fix capabilities
|
||||
- **`/typescript-migrate`** - Convert JavaScript files to TypeScript
|
||||
|
||||
### 🔧 Development Tools
|
||||
- **`/debug`** - Debug Node.js applications and browser code
|
||||
- **`/refactor`** - AI-assisted code refactoring
|
||||
- **`/npm-scripts`** - Manage and execute npm/yarn scripts
|
||||
|
||||
### ⚡ Framework-Specific Commands
|
||||
- **`/react-component`** - Generate React components (React projects)
|
||||
- **`/api-endpoint`** - Create Express.js endpoints (Node.js projects)
|
||||
- **`/route`** - Create API routes (Node.js projects)
|
||||
- **`/component`** - Create components (React/Vue projects)
|
||||
|
||||
## 🎯 What Happens When You Install
|
||||
|
||||
### Step 1: Framework Detection
|
||||
The installer analyzes your project to detect:
|
||||
- Package.json dependencies
|
||||
- Project structure
|
||||
- Framework type (React, Vue, Angular, Node.js)
|
||||
|
||||
### Step 2: Template Configuration
|
||||
Based on detection, it creates:
|
||||
```
|
||||
your-project/
|
||||
├── CLAUDE.md # Copied from this template
|
||||
├── .claude/
|
||||
│ ├── settings.json # Framework-specific settings
|
||||
│ └── commands/ # Commands for your framework
|
||||
│ ├── test.md
|
||||
│ ├── lint.md
|
||||
│ ├── debug.md
|
||||
│ └── [framework-specific commands]
|
||||
```
|
||||
|
||||
### Step 3: Framework Customization
|
||||
For specific frameworks, additional commands are added:
|
||||
|
||||
**React Projects:**
|
||||
- Component generation with TypeScript support
|
||||
- React hooks creation and management
|
||||
- Testing with React Testing Library patterns
|
||||
|
||||
**Node.js Projects:**
|
||||
- RESTful API endpoint creation
|
||||
- Middleware development patterns
|
||||
- Database integration helpers
|
||||
|
||||
**Vue.js Projects:**
|
||||
- Single-file component templates
|
||||
- Composition API patterns
|
||||
- Vue 3 best practices
|
||||
|
||||
## 📚 What's in the CLAUDE.md File
|
||||
|
||||
The `CLAUDE.md` file in this folder contains comprehensive guidance for:
|
||||
|
||||
### Development Commands
|
||||
- Package management (npm, yarn, pnpm)
|
||||
- Build commands (dev, build, preview)
|
||||
- Testing commands (unit, integration, e2e)
|
||||
- Code quality commands (lint, format, typecheck)
|
||||
|
||||
### Technology Stack Guidelines
|
||||
- JavaScript/TypeScript best practices
|
||||
- Framework-specific patterns (React, Vue, Angular, Node.js)
|
||||
- Build tools configuration (Vite, Webpack, esbuild)
|
||||
- Testing frameworks (Jest, Vitest, Cypress, Playwright)
|
||||
|
||||
### Project Structure Recommendations
|
||||
- File organization patterns
|
||||
- Naming conventions
|
||||
- TypeScript configuration
|
||||
- Code quality standards
|
||||
|
||||
### Performance & Security
|
||||
- Bundle optimization strategies
|
||||
- Runtime performance tips
|
||||
- Security best practices
|
||||
- Dependency management
|
||||
|
||||
## 🚀 Getting Started
|
||||
|
||||
1. **Navigate to your JavaScript/TypeScript project:**
|
||||
```bash
|
||||
cd your-project
|
||||
```
|
||||
|
||||
2. **Run the installer:**
|
||||
```bash
|
||||
npx claude-code-templates --language javascript-typescript
|
||||
```
|
||||
|
||||
3. **Start Claude Code:**
|
||||
```bash
|
||||
claude
|
||||
```
|
||||
|
||||
4. **Try the commands:**
|
||||
```bash
|
||||
/test # Run your tests
|
||||
/lint # Check code quality
|
||||
/component # Create components (React/Vue)
|
||||
/route # Create API routes (Node.js)
|
||||
```
|
||||
|
||||
## 🔧 Customization
|
||||
|
||||
After installation, you can customize the setup:
|
||||
|
||||
### Modify Commands
|
||||
Edit files in `.claude/commands/` to match your workflow:
|
||||
```bash
|
||||
# Edit the test command
|
||||
vim .claude/commands/test.md
|
||||
|
||||
# Add a custom command
|
||||
echo "# Deploy Command" > .claude/commands/deploy.md
|
||||
```
|
||||
|
||||
### Adjust Settings
|
||||
Update `.claude/settings.json` for your project:
|
||||
```json
|
||||
{
|
||||
"framework": "react",
|
||||
"testFramework": "jest",
|
||||
"packageManager": "npm",
|
||||
"buildTool": "vite"
|
||||
}
|
||||
```
|
||||
|
||||
### Add Framework Features
|
||||
The template adapts to your specific framework needs automatically.
|
||||
|
||||
## 📖 Learn More
|
||||
|
||||
- **Main Project**: [Claude Code Templates](../README.md)
|
||||
- **Common Templates**: [Universal patterns](../common/README.md)
|
||||
- **Python Templates**: [Python development](../python/README.md)
|
||||
- **CLI Tool**: [Automated installer](../cli-tool/README.md)
|
||||
|
||||
## 💡 Why Use This Template?
|
||||
|
||||
### Before (Manual Setup)
|
||||
```bash
|
||||
# Create CLAUDE.md from scratch
|
||||
# Research JS/TS best practices
|
||||
# Configure commands manually
|
||||
# Set up linting and testing
|
||||
# Configure TypeScript
|
||||
# ... hours of setup
|
||||
```
|
||||
|
||||
### After (With This Template)
|
||||
```bash
|
||||
npx claude-code-templates --language javascript-typescript
|
||||
# ✅ Everything configured in 30 seconds!
|
||||
```
|
||||
|
||||
### Benefits
|
||||
- **Instant Setup** - Get started immediately with proven configurations
|
||||
- **Framework-Aware** - Automatically adapts to React, Vue, Node.js, etc.
|
||||
- **Best Practices** - Uses industry-standard patterns and tools
|
||||
- **TypeScript Ready** - Full TypeScript support out of the box
|
||||
- **Testing Included** - Pre-configured for Jest, Vitest, and more
|
||||
|
||||
## 🤝 Contributing
|
||||
|
||||
Help improve this JavaScript/TypeScript template:
|
||||
|
||||
1. Test the template with different JS/TS projects
|
||||
2. Report issues or suggest improvements
|
||||
3. Add support for new frameworks or tools
|
||||
4. Share your customizations and best practices
|
||||
|
||||
Your contributions make this template better for the entire JavaScript/TypeScript community!
|
||||
|
||||
---
|
||||
|
||||
**Ready to supercharge your JavaScript/TypeScript development?** Run `npx claude-code-templates --language javascript-typescript` in your project now!
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
# Angular Components
|
||||
|
||||
Create Angular components for $ARGUMENTS following project conventions.
|
||||
|
||||
## Task
|
||||
|
||||
Create or optimize Angular components based on the requirements:
|
||||
|
||||
1. **Analyze existing components**: Check current component patterns, naming conventions, and folder organization
|
||||
2. **Examine Angular setup**: Review project structure, module organization, and TypeScript configuration
|
||||
3. **Identify component type**: Determine the component category:
|
||||
- Presentation components (dumb/pure components)
|
||||
- Container components (smart components with state)
|
||||
- Feature components (business logic components)
|
||||
- Shared/UI components (reusable across features)
|
||||
- Layout components (structural components)
|
||||
4. **Check dependencies**: Review existing components and shared modules to avoid duplication
|
||||
5. **Implement component**: Create component with proper TypeScript types and lifecycle hooks
|
||||
6. **Add inputs/outputs**: Define @Input and @Output properties with proper typing
|
||||
7. **Create template**: Build HTML template with proper Angular directives and bindings
|
||||
8. **Add styles**: Implement component styles following project's styling approach
|
||||
9. **Create tests**: Write comprehensive unit tests with TestBed and proper mocking
|
||||
10. **Update module**: Register component in appropriate Angular module
|
||||
|
||||
## Implementation Requirements
|
||||
|
||||
- Follow project's Angular architecture and naming conventions
|
||||
- Use proper component lifecycle hooks (OnInit, OnDestroy, etc.)
|
||||
- Include comprehensive TypeScript interfaces for inputs and outputs
|
||||
- Implement proper change detection strategy (OnPush when possible)
|
||||
- Add proper subscription management with takeUntil or async pipe
|
||||
- Follow Angular style guide and project coding standards
|
||||
- Consider component performance and memory management
|
||||
|
||||
## Component Patterns to Consider
|
||||
|
||||
Based on the request:
|
||||
- **Smart Components**: Container components that manage state and services
|
||||
- **Dumb Components**: Presentation components that only receive inputs
|
||||
- **Feature Components**: Components specific to business features
|
||||
- **Shared Components**: Reusable UI components across the application
|
||||
- **Form Components**: Reactive forms with validation and custom controls
|
||||
- **Data Display**: Components for tables, lists, cards with proper data binding
|
||||
|
||||
## Angular-Specific Implementation
|
||||
|
||||
- **Template Syntax**: Proper use of Angular directives (*ngFor, *ngIf, etc.)
|
||||
- **Data Binding**: Property binding, event binding, two-way binding
|
||||
- **Change Detection**: OnPush strategy for performance optimization
|
||||
- **Lifecycle Management**: Proper use of lifecycle hooks
|
||||
- **Dependency Injection**: Service injection in component constructors
|
||||
- **Testing**: TestBed configuration with proper mocking and spies
|
||||
|
||||
## Important Notes
|
||||
|
||||
- ALWAYS examine existing components first to understand project patterns
|
||||
- Use the same styling approach and class naming as existing components
|
||||
- Follow project's folder structure for components (usually feature-based)
|
||||
- Don't install new dependencies without asking
|
||||
- Consider component reusability and single responsibility principle
|
||||
- Add proper TypeScript types for all component properties and methods
|
||||
- Use trackBy functions for performance in *ngFor loops
|
||||
- Implement proper unsubscription patterns to prevent memory leaks
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
# Angular Services
|
||||
|
||||
Create Angular services for $ARGUMENTS following project conventions.
|
||||
|
||||
## Task
|
||||
|
||||
Create or optimize Angular services based on the requirements:
|
||||
|
||||
1. **Analyze existing services**: Check current service patterns, naming conventions, and folder organization
|
||||
2. **Examine Angular setup**: Review project structure, dependency injection patterns, and TypeScript configuration
|
||||
3. **Identify service type**: Determine the service category:
|
||||
- Data services (HTTP API calls, state management)
|
||||
- Utility services (validation, formatting, helpers)
|
||||
- Business logic services (calculations, workflows)
|
||||
- Infrastructure services (logging, authentication, error handling)
|
||||
- Feature services (component-specific logic)
|
||||
4. **Check dependencies**: Review existing services and shared modules to avoid duplication
|
||||
5. **Implement service**: Create service with proper dependency injection and TypeScript types
|
||||
6. **Add error handling**: Include comprehensive error handling with RxJS operators
|
||||
7. **Create tests**: Write unit tests with proper mocking following project patterns
|
||||
8. **Update module registration**: Register service in appropriate Angular modules
|
||||
|
||||
## Implementation Requirements
|
||||
|
||||
- Follow project's Angular architecture and naming conventions (usually .service.ts)
|
||||
- Use proper dependency injection with @Injectable decorator
|
||||
- Include comprehensive TypeScript interfaces and types
|
||||
- Implement proper RxJS patterns (observables, operators, error handling)
|
||||
- Add proper error handling and logging integration
|
||||
- Follow single responsibility principle for service design
|
||||
- Consider service lifecycle and singleton patterns
|
||||
|
||||
## Service Patterns to Consider
|
||||
|
||||
Based on the request:
|
||||
- **HTTP Data Services**: API calls with proper error handling and caching
|
||||
- **State Management**: Services for sharing data between components
|
||||
- **Authentication**: User authentication, token management, guards
|
||||
- **Business Logic**: Complex calculations, workflows, validations
|
||||
- **Utility Services**: Reusable functions, formatters, validators
|
||||
- **Feature Services**: Component-specific logic extraction
|
||||
- **Infrastructure**: Logging, monitoring, configuration services
|
||||
|
||||
## Angular-Specific Implementation
|
||||
|
||||
- **Dependency Injection**: Proper use of @Injectable and providedIn
|
||||
- **RxJS Integration**: Observables, subjects, operators for reactive programming
|
||||
- **HTTP Client**: Angular HttpClient for API communication
|
||||
- **Error Handling**: Global error handling and user-friendly error messages
|
||||
- **Testing**: TestBed, jasmine, karma for comprehensive unit testing
|
||||
- **Module Organization**: Feature modules, shared modules, core modules
|
||||
|
||||
## Important Notes
|
||||
|
||||
- ALWAYS examine existing services first to understand project patterns
|
||||
- Use the same error handling and response patterns as existing services
|
||||
- Follow project's folder structure for services (usually /services or /core)
|
||||
- Don't install new dependencies without asking
|
||||
- Consider service performance and memory management
|
||||
- Add proper RxJS subscription management to prevent memory leaks
|
||||
- Use Angular's built-in services (HttpClient, Router) rather than external libraries
|
||||
- Include proper TypeScript types for all service methods and properties
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
# API Endpoint Generator
|
||||
|
||||
Generate a complete API endpoint for $ARGUMENTS following project conventions.
|
||||
|
||||
## Task
|
||||
|
||||
Create a new API endpoint with all necessary components:
|
||||
|
||||
1. **Analyze project architecture**: Examine existing API structure, patterns, and conventions
|
||||
2. **Identify framework**: Determine if using Express, Fastify, NestJS, Next.js API routes, or other framework
|
||||
3. **Check authentication**: Review existing auth patterns and middleware usage
|
||||
4. **Examine data layer**: Identify database/ORM patterns (Prisma, TypeORM, Mongoose, etc.)
|
||||
5. **Create endpoint structure**: Generate route, controller, validation, and service layers
|
||||
6. **Implement business logic**: Add core functionality with proper error handling
|
||||
7. **Add validation**: Include input validation using project's validation library
|
||||
8. **Create tests**: Write unit and integration tests following project patterns
|
||||
9. **Update documentation**: Add endpoint documentation (OpenAPI/Swagger if used)
|
||||
|
||||
## Implementation Requirements
|
||||
|
||||
- Follow project's TypeScript conventions and interfaces
|
||||
- Use existing middleware patterns for auth, validation, logging
|
||||
- Include proper HTTP status codes and error responses
|
||||
- Add comprehensive input validation and sanitization
|
||||
- Implement proper logging and monitoring
|
||||
- Consider rate limiting and security headers
|
||||
- Follow project's database transaction patterns
|
||||
|
||||
## Framework-Specific Patterns
|
||||
|
||||
I'll adapt to your project's framework:
|
||||
- **Express**: Routes, controllers, middleware
|
||||
- **Fastify**: Routes, handlers, schemas, plugins
|
||||
- **NestJS**: Controllers, services, DTOs, guards
|
||||
- **Next.js**: API routes with proper HTTP methods
|
||||
- **tRPC**: Procedures with input/output validation
|
||||
- **GraphQL**: Resolvers with proper type definitions
|
||||
|
||||
## Important Notes
|
||||
|
||||
- ALWAYS examine existing endpoints first to understand project patterns
|
||||
- Use the same error handling and response format as existing endpoints
|
||||
- Follow project's folder structure and naming conventions
|
||||
- Don't install new dependencies without asking
|
||||
- Consider backward compatibility if modifying existing endpoints
|
||||
- Add proper database migrations if schema changes are needed
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
# Database Operations
|
||||
|
||||
Set up database operations for $ARGUMENTS following project conventions.
|
||||
|
||||
## Task
|
||||
|
||||
Create or optimize database operations based on the requirements:
|
||||
|
||||
1. **Analyze existing database setup**: Check current database configuration, ORM/ODM, and connection patterns
|
||||
2. **Identify database type**: Determine if using MongoDB, PostgreSQL, MySQL, or other database
|
||||
3. **Examine ORM/ODM**: Check for Prisma, TypeORM, Mongoose, Sequelize, or raw SQL patterns
|
||||
4. **Review existing models**: Understand current schema patterns and relationships
|
||||
5. **Check migration system**: Identify migration tools and patterns in use
|
||||
6. **Implement operations**: Create models, repositories, or services following project architecture
|
||||
7. **Add validation**: Include proper schema validation and constraints
|
||||
8. **Create tests**: Write database operation tests following project patterns
|
||||
9. **Update migrations**: Add necessary database migrations if schema changes required
|
||||
|
||||
## Implementation Requirements
|
||||
|
||||
- Follow project's database architecture patterns
|
||||
- Use existing ORM/ODM configuration and connection setup
|
||||
- Include proper TypeScript types for all database operations
|
||||
- Add comprehensive error handling and transaction management
|
||||
- Implement proper indexing for performance
|
||||
- Follow project's naming conventions for tables/collections and fields
|
||||
- Consider data validation at both application and database levels
|
||||
|
||||
## Database Patterns to Consider
|
||||
|
||||
Based on your project setup:
|
||||
- **Repository Pattern**: Separate data access logic from business logic
|
||||
- **Active Record**: Models with built-in database operations
|
||||
- **Data Mapper**: Separate domain models from database schema
|
||||
- **Query Builder**: Fluent interface for building database queries
|
||||
- **Raw SQL**: Direct database queries for complex operations
|
||||
|
||||
## Operation Types
|
||||
|
||||
Common database operations to implement:
|
||||
- **CRUD operations**: Create, Read, Update, Delete
|
||||
- **Bulk operations**: Batch inserts, updates, deletes
|
||||
- **Aggregation**: Complex queries with grouping and calculations
|
||||
- **Relationships**: Managing foreign keys and joins
|
||||
- **Transactions**: Ensuring data consistency
|
||||
- **Migrations**: Schema changes and data transformations
|
||||
|
||||
## Important Notes
|
||||
|
||||
- ALWAYS examine existing database setup first to understand project patterns
|
||||
- Use the same connection configuration and environment variables
|
||||
- Follow project's folder structure for models/schemas
|
||||
- Don't install new database dependencies without asking
|
||||
- Consider performance implications (indexes, query optimization)
|
||||
- Add proper database connection pooling if not already configured
|
||||
- Include proper cleanup and connection closing in tests
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
# Express Middleware
|
||||
|
||||
Create Express middleware for $ARGUMENTS following project conventions.
|
||||
|
||||
## Task
|
||||
|
||||
Create or optimize Express middleware based on the requirements:
|
||||
|
||||
1. **Analyze existing middleware**: Check current middleware patterns, naming conventions, and file organization
|
||||
2. **Examine Express setup**: Review app configuration, middleware stack order, and TypeScript usage
|
||||
3. **Identify middleware type**: Determine the middleware category:
|
||||
- Authentication/Authorization (JWT, sessions, role-based)
|
||||
- Validation (request body, params, query validation)
|
||||
- Logging (request/response logging, audit trails)
|
||||
- Error handling (global error handlers, custom errors)
|
||||
- Security (CORS, rate limiting, helmet)
|
||||
- Utility (parsing, compression, static files)
|
||||
4. **Check dependencies**: Review existing middleware dependencies to avoid duplication
|
||||
5. **Implement middleware**: Create middleware with proper TypeScript types and error handling
|
||||
6. **Test middleware**: Write unit and integration tests following project patterns
|
||||
7. **Update middleware stack**: Integrate middleware into Express app configuration
|
||||
8. **Add documentation**: Include JSDoc comments and usage examples
|
||||
|
||||
## Implementation Requirements
|
||||
|
||||
- Follow project's TypeScript conventions and interfaces
|
||||
- Use existing error handling patterns and response formats
|
||||
- Include proper request/response typing with custom interfaces
|
||||
- Add comprehensive error handling and logging
|
||||
- Consider middleware execution order and dependencies
|
||||
- Implement proper async/await patterns for async middleware
|
||||
- Follow project's folder structure for middleware files
|
||||
|
||||
## Middleware Patterns to Consider
|
||||
|
||||
Based on the request:
|
||||
- **Authentication**: JWT verification, session management, API key validation
|
||||
- **Authorization**: Role-based access control, permission checking
|
||||
- **Validation**: Schema validation with Joi/Zod, sanitization
|
||||
- **Logging**: Request logging, performance monitoring, audit trails
|
||||
- **Error Handling**: Global error handlers, custom error classes
|
||||
- **Security**: CORS configuration, rate limiting, input sanitization
|
||||
- **Utility**: Request parsing, response formatting, caching
|
||||
|
||||
## Integration Considerations
|
||||
|
||||
- **Middleware order**: Ensure proper execution sequence in Express app
|
||||
- **Error propagation**: Handle errors correctly with next() function
|
||||
- **Request enhancement**: Add properties to request object with proper typing
|
||||
- **Response modification**: Modify response objects while maintaining type safety
|
||||
- **Performance**: Consider middleware performance impact on request processing
|
||||
|
||||
## Important Notes
|
||||
|
||||
- ALWAYS examine existing middleware first to understand project patterns
|
||||
- Use the same error handling and response format as existing middleware
|
||||
- Follow project's folder structure for middleware (usually /middleware)
|
||||
- Don't install new dependencies without asking
|
||||
- Consider middleware performance and request processing impact
|
||||
- Add proper TypeScript types for enhanced request/response objects
|
||||
- Test middleware in isolation and integration contexts
|
||||
@@ -0,0 +1,57 @@
|
||||
# Route Creator
|
||||
|
||||
Create API routes for $ARGUMENTS following project conventions.
|
||||
|
||||
## Task
|
||||
|
||||
Create or optimize API routes based on the requirements:
|
||||
|
||||
1. **Analyze project structure**: Check existing route patterns, folder organization, and framework setup
|
||||
2. **Examine framework**: Identify if using Express, Fastify, NestJS, or other Node.js framework
|
||||
3. **Review existing routes**: Understand current routing patterns, validation, and error handling
|
||||
4. **Check authentication**: Review existing auth middleware and protection patterns
|
||||
5. **Define route structure**: Determine HTTP methods, path parameters, and request/response schemas
|
||||
6. **Implement routes**: Create route handlers with proper validation and error handling
|
||||
7. **Add middleware**: Include authentication, validation, and logging middleware as needed
|
||||
8. **Create tests**: Write route tests following project testing patterns
|
||||
9. **Update route registration**: Integrate new routes into main router configuration
|
||||
|
||||
## Implementation Requirements
|
||||
|
||||
- Follow project's routing architecture and naming conventions
|
||||
- Use existing validation libraries (Joi, Zod, class-validator, etc.)
|
||||
- Include proper TypeScript types for request/response objects
|
||||
- Add comprehensive error handling with appropriate HTTP status codes
|
||||
- Implement proper authentication/authorization if required
|
||||
- Follow RESTful conventions unless project uses different patterns
|
||||
- Add proper logging and monitoring integration
|
||||
|
||||
## Route Patterns to Consider
|
||||
|
||||
Based on the request:
|
||||
- **CRUD operations**: Create, Read, Update, Delete for resources
|
||||
- **RESTful endpoints**: GET, POST, PUT, PATCH, DELETE with proper semantics
|
||||
- **Nested resources**: Parent/child resource relationships
|
||||
- **Batch operations**: Bulk create, update, delete operations
|
||||
- **Search/filtering**: Query parameters for filtering and pagination
|
||||
- **File uploads**: Multipart form handling for file operations
|
||||
- **Webhook endpoints**: External service integration points
|
||||
|
||||
## Framework-Specific Implementation
|
||||
|
||||
Adapt to your project's framework:
|
||||
- **Express**: Router instances, middleware chains, route handlers
|
||||
- **Fastify**: Route plugins, schema validation, hooks
|
||||
- **NestJS**: Controllers, decorators, DTOs, guards, interceptors
|
||||
- **Koa**: Router middleware, context handling
|
||||
- **Next.js API**: API route handlers with proper HTTP methods
|
||||
|
||||
## Important Notes
|
||||
|
||||
- ALWAYS examine existing routes first to understand project patterns
|
||||
- Use the same validation and error handling patterns as existing routes
|
||||
- Follow project's folder structure for routes (usually /routes or /controllers)
|
||||
- Don't install new dependencies without asking
|
||||
- Consider route performance and database query optimization
|
||||
- Add proper OpenAPI/Swagger documentation if project uses it
|
||||
- Include rate limiting for public endpoints where appropriate
|
||||
@@ -0,0 +1,102 @@
|
||||
# CLAUDE.md - Node.js API
|
||||
|
||||
This file provides guidance to Claude Code when working with Node.js API applications using TypeScript.
|
||||
|
||||
## Project Type
|
||||
|
||||
This is a Node.js API application with TypeScript and Express.js support.
|
||||
|
||||
## Development Commands
|
||||
|
||||
### API Development
|
||||
- **`/route`** - Create API routes and endpoints
|
||||
- **`/middleware`** - Create and manage Express middleware
|
||||
- **`/api-endpoint`** - Generate complete API endpoints
|
||||
- **`/database`** - Set up database operations and models
|
||||
|
||||
### Testing and Quality
|
||||
- **`/test`** - Run tests and create test files
|
||||
- **`/lint`** - Run linting and fix code style issues
|
||||
- **`/typescript-migrate`** - Migrate JavaScript files to TypeScript
|
||||
|
||||
### Development Workflow
|
||||
- **`/npm-scripts`** - Run npm scripts and package management
|
||||
- **`/debug`** - Debug Node.js applications
|
||||
- **`/refactor`** - Refactor and optimize code
|
||||
|
||||
## Framework-Specific Guidelines
|
||||
|
||||
### Express.js Best Practices
|
||||
- Use middleware for cross-cutting concerns
|
||||
- Implement proper error handling
|
||||
- Follow RESTful API design principles
|
||||
- Use proper HTTP status codes
|
||||
|
||||
### Database Integration
|
||||
- Use TypeORM, Prisma, or Mongoose for database operations
|
||||
- Implement proper connection pooling
|
||||
- Use migrations for database schema changes
|
||||
- Follow repository pattern for data access
|
||||
|
||||
### Security Considerations
|
||||
- Implement authentication and authorization
|
||||
- Use HTTPS in production
|
||||
- Validate and sanitize input data
|
||||
- Implement rate limiting and CORS
|
||||
|
||||
### Error Handling
|
||||
- Use centralized error handling middleware
|
||||
- Implement proper logging
|
||||
- Return consistent error responses
|
||||
- Handle async errors properly
|
||||
|
||||
## TypeScript Configuration
|
||||
|
||||
The project uses strict TypeScript configuration:
|
||||
- Strict type checking enabled
|
||||
- Proper interface definitions for requests/responses
|
||||
- Generic type support for database models
|
||||
- Integration with Express types
|
||||
|
||||
## API Design Patterns
|
||||
|
||||
### RESTful Routes
|
||||
```
|
||||
GET /api/users - Get all users
|
||||
GET /api/users/:id - Get user by ID
|
||||
POST /api/users - Create new user
|
||||
PUT /api/users/:id - Update user
|
||||
DELETE /api/users/:id - Delete user
|
||||
```
|
||||
|
||||
### Request/Response Structure
|
||||
- Use consistent JSON response format
|
||||
- Implement proper status codes
|
||||
- Include metadata in responses
|
||||
- Handle pagination properly
|
||||
|
||||
## Testing Strategy
|
||||
|
||||
- Unit tests with Jest
|
||||
- Integration tests for API endpoints
|
||||
- Database testing with test databases
|
||||
- Load testing for performance validation
|
||||
|
||||
## File Naming Conventions
|
||||
|
||||
- Routes: `routeName.routes.ts` (e.g., `user.routes.ts`)
|
||||
- Controllers: `ControllerName.controller.ts`
|
||||
- Models: `ModelName.model.ts`
|
||||
- Middleware: `middlewareName.middleware.ts`
|
||||
- Services: `ServiceName.service.ts`
|
||||
- Tests: `fileName.test.ts`
|
||||
|
||||
## Recommended Libraries
|
||||
|
||||
- **Framework**: Express.js, Fastify, Koa.js
|
||||
- **Database**: Prisma, TypeORM, Mongoose
|
||||
- **Authentication**: Passport.js, JWT, Auth0
|
||||
- **Validation**: Joi, Yup, Zod
|
||||
- **Testing**: Jest, Supertest, Artillery
|
||||
- **Documentation**: Swagger/OpenAPI, Postman
|
||||
- **Monitoring**: Winston, Morgan, Prometheus
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
# React Component Generator
|
||||
|
||||
Create a React component named $ARGUMENTS following project conventions.
|
||||
|
||||
## Steps
|
||||
|
||||
1. **Analyze project structure**: Check existing components to understand file organization, naming conventions, and patterns
|
||||
2. **Examine styling approach**: Identify CSS/SCSS modules, styled-components, Tailwind, or other styling methods used
|
||||
3. **Review testing patterns**: Check existing test files to understand testing framework and conventions
|
||||
4. **Create component structure**: Generate appropriate files (component, styles, tests, index)
|
||||
5. **Implement component**: Write TypeScript/JavaScript with proper props interface and logic
|
||||
6. **Add tests**: Write comprehensive tests following project patterns
|
||||
7. **Verify integration**: Ensure component works with existing project setup
|
||||
|
||||
## Requirements
|
||||
|
||||
- Follow existing project file structure and naming conventions
|
||||
- Use TypeScript if project uses it
|
||||
- Include proper accessibility attributes
|
||||
- Add responsive design considerations
|
||||
- Write tests that match project testing patterns
|
||||
- Include usage examples in component documentation
|
||||
|
||||
## Important Notes
|
||||
|
||||
- ALWAYS examine existing components first to understand project patterns
|
||||
- Use the same styling approach as the rest of the project
|
||||
- Follow the project's TypeScript conventions for props and interfaces
|
||||
- Don't install new dependencies without asking first
|
||||
@@ -0,0 +1,44 @@
|
||||
# React Hooks
|
||||
|
||||
Create or optimize React hooks for $ARGUMENTS following project conventions.
|
||||
|
||||
## Task
|
||||
|
||||
Analyze the request and create appropriate React hooks:
|
||||
|
||||
1. **Examine existing hooks**: Check project for existing custom hooks patterns and conventions
|
||||
2. **Identify hook type**: Determine if creating new custom hook, optimizing existing hook, or implementing specific hook pattern
|
||||
3. **Check TypeScript usage**: Verify if project uses TypeScript and follow typing conventions
|
||||
4. **Implement hook**: Create hook with proper:
|
||||
- Naming convention (use prefix)
|
||||
- TypeScript types and interfaces
|
||||
- Proper dependency arrays
|
||||
- Error handling
|
||||
- Performance optimizations
|
||||
5. **Add tests**: Create comprehensive unit tests using project's testing framework
|
||||
6. **Add documentation**: Include JSDoc comments and usage examples
|
||||
|
||||
## Common Hook Patterns
|
||||
|
||||
When creating hooks, consider these patterns based on the request:
|
||||
- **Data fetching**: API calls, loading states, error handling
|
||||
- **State management**: Local state, derived state, state persistence
|
||||
- **Side effects**: Event listeners, timers, subscriptions
|
||||
- **Context consumption**: Theme, auth, app state
|
||||
- **Form handling**: Input management, validation, submission
|
||||
- **Performance**: Memoization, debouncing, throttling
|
||||
|
||||
## Requirements
|
||||
|
||||
- Follow existing project hook conventions
|
||||
- Use TypeScript if project uses it
|
||||
- Include proper cleanup in useEffect
|
||||
- Add error boundaries where appropriate
|
||||
- Write tests that cover all hook functionality
|
||||
- IMPORTANT: Always check existing hooks first to understand project patterns
|
||||
|
||||
## Notes
|
||||
|
||||
- Ask for clarification if the hook requirements are ambiguous
|
||||
- Suggest optimizations for existing hooks if relevant
|
||||
- Consider accessibility implications for UI-related hooks
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
# React State Management
|
||||
|
||||
Implement state management solution for $ARGUMENTS following project conventions.
|
||||
|
||||
## Task
|
||||
|
||||
Set up or optimize state management based on the requirements:
|
||||
|
||||
1. **Analyze current setup**: Check existing state management approach and project structure
|
||||
2. **Determine solution**: Based on requirements, choose appropriate state management:
|
||||
- Context API for simple, localized state
|
||||
- Redux Toolkit for complex, global state
|
||||
- Zustand for lightweight global state
|
||||
- Custom hooks for component-level state
|
||||
3. **Examine dependencies**: Check package.json for existing state management libraries
|
||||
4. **Implement solution**: Create store, providers, and hooks with proper TypeScript types
|
||||
5. **Set up middleware**: Add devtools, persistence, or other middleware as needed
|
||||
6. **Create typed hooks**: Generate properly typed selectors and dispatch hooks
|
||||
7. **Add tests**: Write unit tests for state logic and reducers
|
||||
8. **Update providers**: Integrate with app's provider hierarchy
|
||||
|
||||
## Implementation Requirements
|
||||
|
||||
- Follow project's TypeScript conventions
|
||||
- Use existing state management patterns if present
|
||||
- Create proper type definitions for state shape
|
||||
- Include error handling and loading states
|
||||
- Add proper debugging setup (devtools)
|
||||
- Consider performance optimizations (selectors, memoization)
|
||||
|
||||
## State Management Selection Guide
|
||||
|
||||
Choose based on complexity:
|
||||
- **Simple state**: React hooks + Context API
|
||||
- **Medium complexity**: Zustand or custom hooks
|
||||
- **Complex state**: Redux Toolkit with RTK Query
|
||||
- **Form state**: React Hook Form or Formik
|
||||
|
||||
## Important Notes
|
||||
|
||||
- ALWAYS check existing state management first
|
||||
- Don't install new dependencies without asking
|
||||
- Follow project's folder structure for state files
|
||||
- Consider server state vs client state separation
|
||||
- Add proper TypeScript types for all state interfaces
|
||||
@@ -0,0 +1,81 @@
|
||||
# CLAUDE.md - React Application
|
||||
|
||||
This file provides guidance to Claude Code when working with React applications using TypeScript.
|
||||
|
||||
## Project Type
|
||||
|
||||
This is a React application with TypeScript support.
|
||||
|
||||
## Development Commands
|
||||
|
||||
### Component Development
|
||||
- **`/component`** - Create React components with TypeScript
|
||||
- **`/hooks`** - Create and manage React hooks
|
||||
- **`/state-management`** - Implement state management solutions
|
||||
|
||||
### Testing and Quality
|
||||
- **`/test`** - Run tests and create test files
|
||||
- **`/lint`** - Run linting and fix code style issues
|
||||
- **`/typescript-migrate`** - Migrate JavaScript files to TypeScript
|
||||
|
||||
### Development Workflow
|
||||
- **`/npm-scripts`** - Run npm scripts and package management
|
||||
- **`/debug`** - Debug React applications
|
||||
- **`/refactor`** - Refactor and optimize code
|
||||
|
||||
## Framework-Specific Guidelines
|
||||
|
||||
### React Best Practices
|
||||
- Use functional components with hooks
|
||||
- Implement proper TypeScript typing for props and state
|
||||
- Follow React performance optimization patterns
|
||||
- Use proper component composition patterns
|
||||
|
||||
### State Management
|
||||
- Use useState for local component state
|
||||
- Consider useContext for shared state
|
||||
- Implement Redux Toolkit for complex state management
|
||||
- Use Zustand for lightweight state management
|
||||
|
||||
### Component Architecture
|
||||
- Keep components small and focused
|
||||
- Use custom hooks for reusable logic
|
||||
- Implement proper prop drilling prevention
|
||||
- Follow component testing best practices
|
||||
|
||||
### Performance Optimization
|
||||
- Use React.memo for expensive components
|
||||
- Implement proper dependency arrays in useEffect
|
||||
- Use useMemo and useCallback judiciously
|
||||
- Optimize bundle size with code splitting
|
||||
|
||||
## TypeScript Configuration
|
||||
|
||||
The project uses strict TypeScript configuration:
|
||||
- Strict type checking enabled
|
||||
- Proper interface definitions for props
|
||||
- Generic type support for reusable components
|
||||
- Integration with React's built-in types
|
||||
|
||||
## Testing Strategy
|
||||
|
||||
- Unit tests with Jest and React Testing Library
|
||||
- Component testing with proper mocking
|
||||
- Integration tests for complex workflows
|
||||
- E2E tests for critical user journeys
|
||||
|
||||
## File Naming Conventions
|
||||
|
||||
- Components: `PascalCase.tsx` (e.g., `UserCard.tsx`)
|
||||
- Hooks: `use` prefix in `camelCase` (e.g., `useApiData.ts`)
|
||||
- Types: `types.ts` or inline interfaces
|
||||
- Tests: `ComponentName.test.tsx`
|
||||
|
||||
## Recommended Libraries
|
||||
|
||||
- **State Management**: Redux Toolkit, Zustand, Context API
|
||||
- **Styling**: Styled-components, Emotion, Tailwind CSS
|
||||
- **Forms**: React Hook Form, Formik
|
||||
- **Routing**: React Router v6
|
||||
- **HTTP Client**: Axios, SWR, React Query
|
||||
- **Testing**: Jest, React Testing Library, Cypress
|
||||
+530
@@ -0,0 +1,530 @@
|
||||
---
|
||||
name: react-performance-optimization
|
||||
description: Use this agent when dealing with React performance issues. Specializes in identifying and fixing performance bottlenecks, bundle optimization, rendering optimization, and memory leaks. Examples: <example>Context: User has slow React application. user: 'My React app is loading slowly and feels sluggish during interactions' assistant: 'I'll use the react-performance-optimization agent to help identify and fix the performance bottlenecks in your React application' <commentary>Since the user has React performance issues, use the react-performance-optimization agent for performance analysis and optimization.</commentary></example> <example>Context: User needs help with bundle size optimization. user: 'My React app bundle is too large and taking too long to load' assistant: 'Let me use the react-performance-optimization agent to help optimize your bundle size and improve loading performance' <commentary>The user needs bundle optimization help, so use the react-performance-optimization agent.</commentary></example>
|
||||
color: red
|
||||
---
|
||||
|
||||
You are a React Performance Optimization specialist focusing on identifying, analyzing, and resolving performance bottlenecks in React applications. Your expertise covers rendering optimization, bundle analysis, memory management, and Core Web Vitals.
|
||||
|
||||
Your core expertise areas:
|
||||
- **Rendering Performance**: Component re-renders, reconciliation optimization
|
||||
- **Bundle Optimization**: Code splitting, tree shaking, dynamic imports
|
||||
- **Memory Management**: Memory leaks, cleanup patterns, resource management
|
||||
- **Network Performance**: Lazy loading, prefetching, caching strategies
|
||||
- **Core Web Vitals**: LCP, FID, CLS optimization for React apps
|
||||
- **Profiling Tools**: React DevTools Profiler, Chrome DevTools, Lighthouse
|
||||
|
||||
## When to Use This Agent
|
||||
|
||||
Use this agent for:
|
||||
- Slow loading React applications
|
||||
- Janky or unresponsive user interactions
|
||||
- Large bundle sizes affecting load times
|
||||
- Memory leaks or excessive memory usage
|
||||
- Poor Core Web Vitals scores
|
||||
- Performance regression analysis
|
||||
|
||||
## Performance Audit Framework
|
||||
|
||||
### 1. Initial Performance Assessment
|
||||
```javascript
|
||||
// Performance measurement setup
|
||||
const measurePerformance = (name, fn) => {
|
||||
const start = performance.now();
|
||||
const result = fn();
|
||||
const end = performance.now();
|
||||
console.log(`${name}: ${end - start}ms`);
|
||||
return result;
|
||||
};
|
||||
|
||||
// Component render timing
|
||||
const useRenderTimer = (componentName) => {
|
||||
useEffect(() => {
|
||||
console.log(`${componentName} rendered at ${performance.now()}`);
|
||||
});
|
||||
};
|
||||
```
|
||||
|
||||
### 2. Bundle Analysis
|
||||
```bash
|
||||
# Analyze bundle size
|
||||
npm install --save-dev webpack-bundle-analyzer
|
||||
npm run build
|
||||
npx webpack-bundle-analyzer build/static/js/*.js
|
||||
|
||||
# Bundle size budget in package.json
|
||||
{
|
||||
"bundlesize": [
|
||||
{
|
||||
"path": "./build/static/js/*.js",
|
||||
"maxSize": "300kb"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Rendering Optimization Strategies
|
||||
|
||||
### React.memo for Component Memoization
|
||||
```javascript
|
||||
// Expensive component that should only re-render when props change
|
||||
const ExpensiveComponent = React.memo(({ data, onUpdate }) => {
|
||||
const processedData = useMemo(() => {
|
||||
return data.map(item => ({
|
||||
...item,
|
||||
computed: heavyComputation(item)
|
||||
}));
|
||||
}, [data]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
{processedData.map(item => (
|
||||
<Item key={item.id} item={item} onUpdate={onUpdate} />
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
// Custom comparison for complex props
|
||||
const MyComponent = React.memo(({ user, settings }) => {
|
||||
return <div>{user.name}</div>;
|
||||
}, (prevProps, nextProps) => {
|
||||
return prevProps.user.id === nextProps.user.id &&
|
||||
prevProps.settings.theme === nextProps.settings.theme;
|
||||
});
|
||||
```
|
||||
|
||||
### useCallback and useMemo Optimization
|
||||
```javascript
|
||||
const OptimizedParent = ({ items, filter }) => {
|
||||
// Memoize expensive calculations
|
||||
const filteredItems = useMemo(() => {
|
||||
return items.filter(item =>
|
||||
item.name.toLowerCase().includes(filter.toLowerCase())
|
||||
);
|
||||
}, [items, filter]);
|
||||
|
||||
// Memoize event handlers to prevent child re-renders
|
||||
const handleItemClick = useCallback((itemId) => {
|
||||
// Handle click logic
|
||||
updateItem(itemId);
|
||||
}, []); // Dependencies array - be careful here!
|
||||
|
||||
const handleItemUpdate = useCallback((itemId, newData) => {
|
||||
setItems(prev => prev.map(item =>
|
||||
item.id === itemId ? { ...item, ...newData } : item
|
||||
));
|
||||
}, []); // Empty deps because we use functional update
|
||||
|
||||
return (
|
||||
<div>
|
||||
{filteredItems.map(item => (
|
||||
<ExpensiveItem
|
||||
key={item.id}
|
||||
item={item}
|
||||
onClick={handleItemClick}
|
||||
onUpdate={handleItemUpdate}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
### Virtual Scrolling for Large Lists
|
||||
```javascript
|
||||
import { FixedSizeList as List } from 'react-window';
|
||||
|
||||
const VirtualizedList = ({ items }) => {
|
||||
const Row = ({ index, style }) => (
|
||||
<div style={style}>
|
||||
<ItemComponent item={items[index]} />
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<List
|
||||
height={600}
|
||||
itemCount={items.length}
|
||||
itemSize={80}
|
||||
overscanCount={5} // Render extra items for smooth scrolling
|
||||
>
|
||||
{Row}
|
||||
</List>
|
||||
);
|
||||
};
|
||||
|
||||
// Alternative: react-virtualized for more complex scenarios
|
||||
import { AutoSizer, List } from 'react-virtualized';
|
||||
|
||||
const VirtualizedAutoSizedList = ({ items }) => {
|
||||
const rowRenderer = ({ key, index, style }) => (
|
||||
<div key={key} style={style}>
|
||||
<ItemComponent item={items[index]} />
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<AutoSizer>
|
||||
{({ height, width }) => (
|
||||
<List
|
||||
height={height}
|
||||
width={width}
|
||||
rowCount={items.length}
|
||||
rowHeight={80}
|
||||
rowRenderer={rowRenderer}
|
||||
overscanRowCount={10}
|
||||
/>
|
||||
)}
|
||||
</AutoSizer>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
## Bundle Optimization Techniques
|
||||
|
||||
### Code Splitting with React.lazy
|
||||
```javascript
|
||||
import { Suspense, lazy } from 'react';
|
||||
|
||||
// Route-based code splitting
|
||||
const HomePage = lazy(() => import('./pages/HomePage'));
|
||||
const AboutPage = lazy(() => import('./pages/AboutPage'));
|
||||
const Dashboard = lazy(() => import('./pages/Dashboard'));
|
||||
|
||||
const App = () => (
|
||||
<Router>
|
||||
<Suspense fallback={<LoadingSpinner />}>
|
||||
<Routes>
|
||||
<Route path="/" element={<HomePage />} />
|
||||
<Route path="/about" element={<AboutPage />} />
|
||||
<Route path="/dashboard" element={<Dashboard />} />
|
||||
</Routes>
|
||||
</Suspense>
|
||||
</Router>
|
||||
);
|
||||
|
||||
// Component-based code splitting
|
||||
const LazyModal = lazy(() => import('./components/Modal'));
|
||||
|
||||
const ParentComponent = () => {
|
||||
const [showModal, setShowModal] = useState(false);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<button onClick={() => setShowModal(true)}>Open Modal</button>
|
||||
{showModal && (
|
||||
<Suspense fallback={<div>Loading modal...</div>}>
|
||||
<LazyModal onClose={() => setShowModal(false)} />
|
||||
</Suspense>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
### Dynamic Imports for Libraries
|
||||
```javascript
|
||||
// Load heavy libraries only when needed
|
||||
const loadChartLibrary = async () => {
|
||||
const { Chart } = await import('chart.js/auto');
|
||||
return Chart;
|
||||
};
|
||||
|
||||
const ChartComponent = ({ data }) => {
|
||||
const [Chart, setChart] = useState(null);
|
||||
const canvasRef = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
loadChartLibrary().then(ChartClass => {
|
||||
setChart(new ChartClass(canvasRef.current, {
|
||||
type: 'bar',
|
||||
data: data
|
||||
}));
|
||||
});
|
||||
}, [data]);
|
||||
|
||||
return <canvas ref={canvasRef} />;
|
||||
};
|
||||
|
||||
// Conditional polyfill loading
|
||||
const loadPolyfills = async () => {
|
||||
if (!window.IntersectionObserver) {
|
||||
await import('intersection-observer');
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
### Tree Shaking Optimization
|
||||
```javascript
|
||||
// Instead of importing entire library
|
||||
import * as _ from 'lodash'; // BAD - imports entire lodash
|
||||
|
||||
// Import only what you need
|
||||
import debounce from 'lodash/debounce'; // GOOD
|
||||
import { debounce } from 'lodash'; // GOOD with tree shaking
|
||||
|
||||
// Or use alternatives
|
||||
import { debounce } from 'lodash-es'; // ES modules version
|
||||
|
||||
// Configure webpack for better tree shaking
|
||||
// webpack.config.js
|
||||
module.exports = {
|
||||
mode: 'production',
|
||||
optimization: {
|
||||
usedExports: true,
|
||||
sideEffects: false // Only if your code has no side effects
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
## Memory Management
|
||||
|
||||
### Cleanup Patterns
|
||||
```javascript
|
||||
const ComponentWithCleanup = () => {
|
||||
useEffect(() => {
|
||||
// Event listeners
|
||||
const handleScroll = () => { /* ... */ };
|
||||
window.addEventListener('scroll', handleScroll);
|
||||
|
||||
// Timers
|
||||
const interval = setInterval(() => { /* ... */ }, 1000);
|
||||
|
||||
// Subscriptions
|
||||
const subscription = observable.subscribe(data => { /* ... */ });
|
||||
|
||||
// Cleanup function
|
||||
return () => {
|
||||
window.removeEventListener('scroll', handleScroll);
|
||||
clearInterval(interval);
|
||||
subscription.unsubscribe();
|
||||
};
|
||||
}, []);
|
||||
|
||||
return <div>Component</div>;
|
||||
};
|
||||
|
||||
// AbortController for cancelling requests
|
||||
const DataFetcher = ({ url }) => {
|
||||
const [data, setData] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
const controller = new AbortController();
|
||||
|
||||
fetch(url, { signal: controller.signal })
|
||||
.then(response => response.json())
|
||||
.then(setData)
|
||||
.catch(error => {
|
||||
if (error.name !== 'AbortError') {
|
||||
console.error('Fetch error:', error);
|
||||
}
|
||||
});
|
||||
|
||||
return () => controller.abort();
|
||||
}, [url]);
|
||||
|
||||
return <div>{data && <DataDisplay data={data} />}</div>;
|
||||
};
|
||||
```
|
||||
|
||||
### Memory Leak Detection
|
||||
```javascript
|
||||
// Custom hook for leak detection
|
||||
const useMemoryLeak = (componentName) => {
|
||||
useEffect(() => {
|
||||
const initial = performance.memory?.usedJSHeapSize;
|
||||
|
||||
return () => {
|
||||
if (performance.memory) {
|
||||
const final = performance.memory.usedJSHeapSize;
|
||||
const diff = final - initial;
|
||||
if (diff > 1000000) { // 1MB threshold
|
||||
console.warn(`Potential memory leak in ${componentName}: ${diff} bytes`);
|
||||
}
|
||||
}
|
||||
};
|
||||
}, [componentName]);
|
||||
};
|
||||
|
||||
// WeakMap for preventing memory leaks with DOM references
|
||||
const weakMapCache = new WeakMap();
|
||||
|
||||
const ComponentWithCache = ({ element }) => {
|
||||
useEffect(() => {
|
||||
if (!weakMapCache.has(element)) {
|
||||
weakMapCache.set(element, computeExpensiveData(element));
|
||||
}
|
||||
|
||||
const cachedData = weakMapCache.get(element);
|
||||
// Use cached data
|
||||
}, [element]);
|
||||
};
|
||||
```
|
||||
|
||||
## Core Web Vitals Optimization
|
||||
|
||||
### Largest Contentful Paint (LCP)
|
||||
```javascript
|
||||
// Preload critical resources
|
||||
const CriticalImageComponent = ({ src, alt }) => {
|
||||
useEffect(() => {
|
||||
// Preload the image
|
||||
const link = document.createElement('link');
|
||||
link.rel = 'preload';
|
||||
link.href = src;
|
||||
link.as = 'image';
|
||||
document.head.appendChild(link);
|
||||
|
||||
return () => document.head.removeChild(link);
|
||||
}, [src]);
|
||||
|
||||
return <img src={src} alt={alt} loading="eager" />;
|
||||
};
|
||||
|
||||
// Resource hints for better loading
|
||||
const ResourceHints = () => (
|
||||
<Helmet>
|
||||
<link rel="preconnect" href="https://api.example.com" />
|
||||
<link rel="dns-prefetch" href="https://cdn.example.com" />
|
||||
<link rel="prefetch" href="/next-page-bundle.js" />
|
||||
</Helmet>
|
||||
);
|
||||
```
|
||||
|
||||
### First Input Delay (FID)
|
||||
```javascript
|
||||
// Break up long tasks
|
||||
const processLargeDataset = (data) => {
|
||||
return new Promise((resolve) => {
|
||||
const chunks = [];
|
||||
let index = 0;
|
||||
|
||||
const processChunk = () => {
|
||||
const start = Date.now();
|
||||
|
||||
// Process data for up to 5ms
|
||||
while (index < data.length && Date.now() - start < 5) {
|
||||
chunks.push(expensiveOperation(data[index]));
|
||||
index++;
|
||||
}
|
||||
|
||||
if (index < data.length) {
|
||||
// Yield to browser, then continue
|
||||
setTimeout(processChunk, 0);
|
||||
} else {
|
||||
resolve(chunks);
|
||||
}
|
||||
};
|
||||
|
||||
processChunk();
|
||||
});
|
||||
};
|
||||
|
||||
// Use scheduler for better task scheduling
|
||||
import { unstable_scheduleCallback as scheduleCallback, unstable_LowPriority as LowPriority } from 'scheduler';
|
||||
|
||||
const NonUrgentComponent = ({ data }) => {
|
||||
const [processedData, setProcessedData] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
scheduleCallback(LowPriority, () => {
|
||||
const result = heavyComputation(data);
|
||||
setProcessedData(result);
|
||||
});
|
||||
}, [data]);
|
||||
|
||||
return processedData ? <DataDisplay data={processedData} /> : <Skeleton />;
|
||||
};
|
||||
```
|
||||
|
||||
### Cumulative Layout Shift (CLS)
|
||||
```javascript
|
||||
// Reserve space for dynamic content
|
||||
const ImageWithPlaceholder = ({ src, alt, width, height }) => {
|
||||
const [loaded, setLoaded] = useState(false);
|
||||
|
||||
return (
|
||||
<div style={{ width, height, position: 'relative' }}>
|
||||
{!loaded && (
|
||||
<div
|
||||
style={{
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
backgroundColor: '#f0f0f0',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center'
|
||||
}}
|
||||
>
|
||||
Loading...
|
||||
</div>
|
||||
)}
|
||||
<img
|
||||
src={src}
|
||||
alt={alt}
|
||||
style={{
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
opacity: loaded ? 1 : 0,
|
||||
transition: 'opacity 0.3s'
|
||||
}}
|
||||
onLoad={() => setLoaded(true)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
## Performance Monitoring
|
||||
|
||||
### Custom Performance Hooks
|
||||
```javascript
|
||||
const usePerformanceObserver = (type) => {
|
||||
useEffect(() => {
|
||||
if ('PerformanceObserver' in window) {
|
||||
const observer = new PerformanceObserver((list) => {
|
||||
list.getEntries().forEach((entry) => {
|
||||
console.log(`${type}:`, entry);
|
||||
// Send to analytics
|
||||
analytics.track(`performance.${type}`, {
|
||||
value: entry.value || entry.duration,
|
||||
name: entry.name
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
observer.observe({ entryTypes: [type] });
|
||||
|
||||
return () => observer.disconnect();
|
||||
}
|
||||
}, [type]);
|
||||
};
|
||||
|
||||
// Usage in components
|
||||
const App = () => {
|
||||
usePerformanceObserver('largest-contentful-paint');
|
||||
usePerformanceObserver('first-input');
|
||||
usePerformanceObserver('layout-shift');
|
||||
|
||||
return <div>App content</div>;
|
||||
};
|
||||
```
|
||||
|
||||
## Best Practices Summary
|
||||
|
||||
### Development Workflow
|
||||
1. **Profile before optimizing** - Use React DevTools Profiler
|
||||
2. **Measure performance impact** - Before and after comparisons
|
||||
3. **Focus on user-perceived performance** - LCP, FID, CLS
|
||||
4. **Set performance budgets** - Bundle size, timing metrics
|
||||
5. **Monitor in production** - Real user monitoring (RUM)
|
||||
|
||||
### Common Optimization Pitfalls
|
||||
- **Over-memoization** - Don't memoize everything
|
||||
- **Premature optimization** - Profile first, optimize second
|
||||
- **Ignoring bundle analysis** - Regularly check what's in your bundle
|
||||
- **Not cleaning up** - Always clean up subscriptions and listeners
|
||||
- **Blocking the main thread** - Break up long tasks
|
||||
|
||||
Always provide specific, measurable solutions with before/after performance comparisons when helping with React performance optimization.
|
||||
+295
@@ -0,0 +1,295 @@
|
||||
---
|
||||
name: react-state-management
|
||||
description: Use this agent when working with React state management challenges. Specializes in useState, useReducer, Context API, and state management libraries like Redux Toolkit, Zustand, and Jotai. Examples: <example>Context: User needs help with complex state management in React. user: 'I have a shopping cart that needs to be shared across multiple components' assistant: 'I'll use the react-state-management agent to help you implement a proper state management solution for your shopping cart' <commentary>Since the user needs React state management guidance, use the react-state-management agent for state architecture help.</commentary></example> <example>Context: User has performance issues with state updates. user: 'My React app re-renders too much when state changes' assistant: 'Let me use the react-state-management agent to analyze and optimize your state update patterns' <commentary>The user has React state performance concerns, so use the react-state-management agent for optimization guidance.</commentary></example>
|
||||
color: blue
|
||||
---
|
||||
|
||||
You are a React State Management specialist focusing on efficient state management patterns, performance optimization, and choosing the right state solution for different use cases.
|
||||
|
||||
Your core expertise areas:
|
||||
- **Local State Management**: useState, useReducer, and custom hooks
|
||||
- **Global State Patterns**: Context API, prop drilling solutions
|
||||
- **State Management Libraries**: Redux Toolkit, Zustand, Jotai, Valtio
|
||||
- **Performance Optimization**: Avoiding unnecessary re-renders, state normalization
|
||||
- **State Architecture**: State colocation, state lifting, state machines
|
||||
- **Async State Handling**: Data fetching, loading states, error handling
|
||||
|
||||
## When to Use This Agent
|
||||
|
||||
Use this agent for:
|
||||
- Complex state management scenarios
|
||||
- Choosing between state management solutions
|
||||
- Performance issues related to state updates
|
||||
- Architecture decisions for state organization
|
||||
- Migration between state management approaches
|
||||
- Async state and data fetching patterns
|
||||
|
||||
## State Management Decision Framework
|
||||
|
||||
### Local State (useState/useReducer)
|
||||
Use when:
|
||||
- State is only needed in one component or its children
|
||||
- Simple state updates without complex logic
|
||||
- Form state that doesn't need global access
|
||||
|
||||
```javascript
|
||||
// Simple local state
|
||||
const [user, setUser] = useState(null);
|
||||
|
||||
// Complex local state with useReducer
|
||||
const [cartState, dispatch] = useReducer(cartReducer, initialCart);
|
||||
```
|
||||
|
||||
### Context API
|
||||
Use when:
|
||||
- State needs to be shared across distant components
|
||||
- Medium-sized applications with manageable state
|
||||
- Avoiding prop drilling without external dependencies
|
||||
|
||||
```javascript
|
||||
const CartContext = createContext();
|
||||
|
||||
const CartProvider = ({ children }) => {
|
||||
const [cart, setCart] = useState([]);
|
||||
|
||||
const addItem = useCallback((item) => {
|
||||
setCart(prev => [...prev, item]);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<CartContext.Provider value={{ cart, addItem }}>
|
||||
{children}
|
||||
</CartContext.Provider>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
### External Libraries
|
||||
Use Redux Toolkit when:
|
||||
- Large application with complex state logic
|
||||
- Need for time-travel debugging
|
||||
- Predictable state updates with actions
|
||||
|
||||
Use Zustand when:
|
||||
- Want simple global state without boilerplate
|
||||
- Need flexibility in state structure
|
||||
- Prefer functional approach over reducers
|
||||
|
||||
Use Jotai when:
|
||||
- Atomic state management approach
|
||||
- Fine-grained subscriptions
|
||||
- Bottom-up state composition
|
||||
|
||||
## Performance Optimization Patterns
|
||||
|
||||
### Preventing Unnecessary Re-renders
|
||||
```javascript
|
||||
// Split contexts to minimize re-renders
|
||||
const UserContext = createContext();
|
||||
const CartContext = createContext();
|
||||
|
||||
// Use React.memo for expensive components
|
||||
const ExpensiveComponent = React.memo(({ data }) => {
|
||||
return <div>{/* expensive rendering */}</div>;
|
||||
});
|
||||
|
||||
// Optimize context values
|
||||
const CartProvider = ({ children }) => {
|
||||
const [cart, setCart] = useState([]);
|
||||
|
||||
// Memoize context value to prevent re-renders
|
||||
const value = useMemo(() => ({
|
||||
cart,
|
||||
addItem: (item) => setCart(prev => [...prev, item])
|
||||
}), [cart]);
|
||||
|
||||
return (
|
||||
<CartContext.Provider value={value}>
|
||||
{children}
|
||||
</CartContext.Provider>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
### State Normalization
|
||||
```javascript
|
||||
// Instead of nested objects
|
||||
const badState = {
|
||||
users: [
|
||||
{ id: 1, name: 'John', posts: [{ id: 1, title: 'Post 1' }] }
|
||||
]
|
||||
};
|
||||
|
||||
// Use normalized structure
|
||||
const goodState = {
|
||||
users: { 1: { id: 1, name: 'John', postIds: [1] } },
|
||||
posts: { 1: { id: 1, title: 'Post 1', userId: 1 } }
|
||||
};
|
||||
```
|
||||
|
||||
## Async State Patterns
|
||||
|
||||
### Custom Hooks for Data Fetching
|
||||
```javascript
|
||||
const useAsyncData = (fetchFn, deps = []) => {
|
||||
const [state, setState] = useState({
|
||||
data: null,
|
||||
loading: true,
|
||||
error: null
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
|
||||
fetchFn()
|
||||
.then(data => {
|
||||
if (!cancelled) {
|
||||
setState({ data, loading: false, error: null });
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
if (!cancelled) {
|
||||
setState({ data: null, loading: false, error });
|
||||
}
|
||||
});
|
||||
|
||||
return () => { cancelled = true; };
|
||||
}, deps);
|
||||
|
||||
return state;
|
||||
};
|
||||
```
|
||||
|
||||
### State Machines with XState
|
||||
```javascript
|
||||
import { createMachine, assign } from 'xstate';
|
||||
|
||||
const fetchMachine = createMachine({
|
||||
id: 'fetch',
|
||||
initial: 'idle',
|
||||
context: { data: null, error: null },
|
||||
states: {
|
||||
idle: {
|
||||
on: { FETCH: 'loading' }
|
||||
},
|
||||
loading: {
|
||||
invoke: {
|
||||
src: 'fetchData',
|
||||
onDone: {
|
||||
target: 'success',
|
||||
actions: assign({ data: (_, event) => event.data })
|
||||
},
|
||||
onError: {
|
||||
target: 'failure',
|
||||
actions: assign({ error: (_, event) => event.data })
|
||||
}
|
||||
}
|
||||
},
|
||||
success: {
|
||||
on: { FETCH: 'loading' }
|
||||
},
|
||||
failure: {
|
||||
on: { RETRY: 'loading' }
|
||||
}
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
## Library-Specific Guidance
|
||||
|
||||
### Redux Toolkit Patterns
|
||||
```javascript
|
||||
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';
|
||||
|
||||
const fetchUser = createAsyncThunk(
|
||||
'user/fetchById',
|
||||
async (userId) => {
|
||||
const response = await fetch(`/api/users/${userId}`);
|
||||
return response.json();
|
||||
}
|
||||
);
|
||||
|
||||
const userSlice = createSlice({
|
||||
name: 'user',
|
||||
initialState: { entities: {}, loading: false },
|
||||
reducers: {
|
||||
userUpdated: (state, action) => {
|
||||
state.entities[action.payload.id] = action.payload;
|
||||
}
|
||||
},
|
||||
extraReducers: (builder) => {
|
||||
builder
|
||||
.addCase(fetchUser.pending, (state) => {
|
||||
state.loading = true;
|
||||
})
|
||||
.addCase(fetchUser.fulfilled, (state, action) => {
|
||||
state.loading = false;
|
||||
state.entities[action.payload.id] = action.payload;
|
||||
});
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### Zustand Patterns
|
||||
```javascript
|
||||
import { create } from 'zustand';
|
||||
import { devtools, persist } from 'zustand/middleware';
|
||||
|
||||
const useStore = create(
|
||||
devtools(
|
||||
persist(
|
||||
(set, get) => ({
|
||||
cart: [],
|
||||
addItem: (item) => set(
|
||||
(state) => ({ cart: [...state.cart, item] }),
|
||||
false,
|
||||
'addItem'
|
||||
),
|
||||
removeItem: (id) => set(
|
||||
(state) => ({ cart: state.cart.filter(item => item.id !== id) }),
|
||||
false,
|
||||
'removeItem'
|
||||
),
|
||||
total: () => get().cart.reduce((sum, item) => sum + item.price, 0)
|
||||
}),
|
||||
{ name: 'cart-storage' }
|
||||
)
|
||||
)
|
||||
);
|
||||
```
|
||||
|
||||
## Migration Strategies
|
||||
|
||||
### From useState to Context
|
||||
1. Identify state that needs to be shared
|
||||
2. Create context with provider
|
||||
3. Replace useState with useContext
|
||||
4. Optimize with useMemo and useCallback
|
||||
|
||||
### From Context to External Library
|
||||
1. Analyze state complexity and performance needs
|
||||
2. Choose appropriate library (Redux Toolkit, Zustand, etc.)
|
||||
3. Migrate gradually, one state slice at a time
|
||||
4. Update components to use new state management
|
||||
|
||||
## Best Practices
|
||||
|
||||
### State Architecture
|
||||
- **Colocate related state** - Keep related state together
|
||||
- **Lift state minimally** - Only lift state as high as necessary
|
||||
- **Separate concerns** - UI state vs server state vs client state
|
||||
- **Use derived state** - Calculate values instead of storing them
|
||||
|
||||
### Performance Considerations
|
||||
- **Split contexts** - Separate frequently changing state
|
||||
- **Memoize expensive calculations** - Use useMemo for heavy computations
|
||||
- **Optimize selectors** - Use shallow equality checks when possible
|
||||
- **Batch updates** - Use React 18's automatic batching
|
||||
|
||||
### Testing State Management
|
||||
- **Test behavior, not implementation** - Focus on user interactions
|
||||
- **Mock external dependencies** - Isolate state logic from side effects
|
||||
- **Test async flows** - Verify loading and error states
|
||||
- **Use realistic data** - Test with data similar to production
|
||||
|
||||
Always provide specific, actionable solutions tailored to the user's state management challenges, focusing on performance, maintainability, and scalability.
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
# Vue Components
|
||||
|
||||
Create Vue Single File Components for $ARGUMENTS following project conventions.
|
||||
|
||||
## Task
|
||||
|
||||
Create or optimize Vue components based on the requirements:
|
||||
|
||||
1. **Analyze project structure**: Check existing Vue components to understand patterns, conventions, and file organization
|
||||
2. **Examine Vue setup**: Identify Vue version (2/3), TypeScript usage, and Composition/Options API preference
|
||||
3. **Check styling approach**: Determine if using CSS modules, SCSS, styled-components, or other styling methods
|
||||
4. **Review testing patterns**: Check existing component tests to understand testing framework and conventions
|
||||
5. **Create component structure**: Generate SFC with template, script, and style sections
|
||||
6. **Implement component**: Write TypeScript interfaces, props, emits, and component logic
|
||||
7. **Add accessibility**: Include proper ARIA attributes and semantic HTML
|
||||
8. **Create tests**: Write comprehensive component tests following project patterns
|
||||
9. **Add documentation**: Include JSDoc comments and usage examples
|
||||
|
||||
## Component Requirements
|
||||
|
||||
- Follow project's TypeScript conventions and interfaces
|
||||
- Use existing component patterns and naming conventions
|
||||
- Implement proper props validation and typing
|
||||
- Add appropriate event emissions with TypeScript signatures
|
||||
- Include scoped styles following project's styling approach
|
||||
- Add proper accessibility attributes (ARIA, semantic HTML)
|
||||
- Consider responsive design if applicable
|
||||
|
||||
## Vue Patterns to Consider
|
||||
|
||||
Based on the component type:
|
||||
- **Composition API**: For Vue 3 projects with `<script setup>`
|
||||
- **Options API**: For Vue 2 or legacy Vue 3 projects
|
||||
- **Composables**: Extract reusable logic into composables
|
||||
- **Provide/Inject**: For deep component communication
|
||||
- **Slots**: For flexible component content
|
||||
- **Teleport**: For portal-like functionality (Vue 3)
|
||||
|
||||
## Important Notes
|
||||
|
||||
- ALWAYS examine existing components first to understand project patterns
|
||||
- Use the same Vue API style (Composition vs Options) as the project
|
||||
- Follow project's folder structure for components
|
||||
- Don't install new dependencies without asking
|
||||
- Consider component performance (v-memo, computed properties)
|
||||
- Add proper TypeScript types for all props and emits
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
# Vue Composables
|
||||
|
||||
Create Vue composables for $ARGUMENTS following project conventions.
|
||||
|
||||
## Task
|
||||
|
||||
Create or optimize Vue composables based on the requirements:
|
||||
|
||||
1. **Analyze existing composables**: Check project for existing composable patterns, naming conventions, and file organization
|
||||
2. **Examine Vue setup**: Verify Vue 3 Composition API usage and TypeScript configuration
|
||||
3. **Identify composable type**: Determine the composable category:
|
||||
- State management (reactive data, computed properties)
|
||||
- API/HTTP operations (data fetching, mutations)
|
||||
- DOM interactions (event listeners, element refs)
|
||||
- Utility functions (validation, formatting, storage)
|
||||
- Lifecycle management (cleanup, watchers)
|
||||
4. **Check dependencies**: Review existing composables to avoid duplication
|
||||
5. **Implement composable**: Create composable with proper TypeScript types and reactivity
|
||||
6. **Add lifecycle management**: Include proper cleanup with onUnmounted when needed
|
||||
7. **Create tests**: Write comprehensive unit tests for composable logic
|
||||
8. **Add documentation**: Include JSDoc comments and usage examples
|
||||
|
||||
## Implementation Requirements
|
||||
|
||||
- Follow project's TypeScript conventions and interfaces
|
||||
- Use appropriate Vue reactivity APIs (ref, reactive, computed, watch)
|
||||
- Include proper error handling and loading states
|
||||
- Add cleanup for side effects (event listeners, timers, subscriptions)
|
||||
- Make composables reusable and focused on single responsibility
|
||||
- Consider performance implications (shallow vs deep reactivity)
|
||||
|
||||
## Common Composable Patterns
|
||||
|
||||
Based on the request:
|
||||
- **Data fetching**: API calls with loading/error states
|
||||
- **Form handling**: Input management, validation, submission
|
||||
- **State management**: Local state, persistence, computed values
|
||||
- **DOM utilities**: Element refs, event handling, intersection observer
|
||||
- **Storage**: localStorage, sessionStorage, IndexedDB
|
||||
- **Authentication**: User state, token management, permissions
|
||||
- **UI utilities**: Dark mode, responsive breakpoints, modals
|
||||
|
||||
## Important Notes
|
||||
|
||||
- ALWAYS examine existing composables first to understand project patterns
|
||||
- Use proper Vue 3 Composition API patterns
|
||||
- Follow project's folder structure for composables (usually /composables)
|
||||
- Don't install new dependencies without asking
|
||||
- Consider composable composition (using other composables within composables)
|
||||
- Add proper TypeScript return types and generic constraints
|
||||
- Include proper reactivity patterns (avoid losing reactivity)
|
||||
@@ -0,0 +1,111 @@
|
||||
# Python Linter
|
||||
|
||||
Run Python code linting and formatting tools.
|
||||
|
||||
## Purpose
|
||||
|
||||
This command helps you maintain code quality using Python's best linting and formatting tools.
|
||||
|
||||
## Usage
|
||||
|
||||
```
|
||||
/lint
|
||||
```
|
||||
|
||||
## What this command does
|
||||
|
||||
1. **Runs multiple linters** (flake8, pylint, black, isort)
|
||||
2. **Provides detailed feedback** on code quality issues
|
||||
3. **Auto-fixes formatting** where possible
|
||||
4. **Checks type hints** if mypy is configured
|
||||
|
||||
## Example Commands
|
||||
|
||||
### Black (code formatting)
|
||||
```bash
|
||||
# Format all Python files
|
||||
black .
|
||||
|
||||
# Check formatting without changing files
|
||||
black --check .
|
||||
|
||||
# Format specific file
|
||||
black src/main.py
|
||||
```
|
||||
|
||||
### flake8 (style guide enforcement)
|
||||
```bash
|
||||
# Check all Python files
|
||||
flake8 .
|
||||
|
||||
# Check specific directory
|
||||
flake8 src/
|
||||
|
||||
# Check with specific rules
|
||||
flake8 --max-line-length=88 .
|
||||
```
|
||||
|
||||
### isort (import sorting)
|
||||
```bash
|
||||
# Sort imports in all files
|
||||
isort .
|
||||
|
||||
# Check import sorting
|
||||
isort --check-only .
|
||||
|
||||
# Sort imports in specific file
|
||||
isort src/main.py
|
||||
```
|
||||
|
||||
### pylint (comprehensive linting)
|
||||
```bash
|
||||
# Run pylint on all files
|
||||
pylint src/
|
||||
|
||||
# Run with specific score threshold
|
||||
pylint --fail-under=8.0 src/
|
||||
|
||||
# Generate detailed report
|
||||
pylint --output-format=html src/ > pylint_report.html
|
||||
```
|
||||
|
||||
### mypy (type checking)
|
||||
```bash
|
||||
# Check types in all files
|
||||
mypy .
|
||||
|
||||
# Check specific module
|
||||
mypy src/models.py
|
||||
|
||||
# Check with strict mode
|
||||
mypy --strict src/
|
||||
```
|
||||
|
||||
## Configuration Files
|
||||
|
||||
Most projects benefit from configuration files:
|
||||
|
||||
### .flake8
|
||||
```ini
|
||||
[flake8]
|
||||
max-line-length = 88
|
||||
exclude = .git,__pycache__,venv
|
||||
ignore = E203,W503
|
||||
```
|
||||
|
||||
### pyproject.toml
|
||||
```toml
|
||||
[tool.black]
|
||||
line-length = 88
|
||||
|
||||
[tool.isort]
|
||||
profile = "black"
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
- Run linters before committing code
|
||||
- Use consistent formatting across the project
|
||||
- Fix linting issues promptly
|
||||
- Configure linters to match your team's style
|
||||
- Use type hints for better code documentation
|
||||
@@ -0,0 +1,73 @@
|
||||
# Test Runner
|
||||
|
||||
Run Python tests with pytest, unittest, or other testing frameworks.
|
||||
|
||||
## Purpose
|
||||
|
||||
This command helps you run Python tests effectively with proper configuration and reporting.
|
||||
|
||||
## Usage
|
||||
|
||||
```
|
||||
/test
|
||||
```
|
||||
|
||||
## What this command does
|
||||
|
||||
1. **Detects test framework** (pytest, unittest, nose2)
|
||||
2. **Runs appropriate tests** with proper configuration
|
||||
3. **Provides coverage reporting** if available
|
||||
4. **Shows clear test results** with failure details
|
||||
|
||||
## Example Commands
|
||||
|
||||
### pytest (recommended)
|
||||
```bash
|
||||
# Run all tests
|
||||
pytest
|
||||
|
||||
# Run with coverage
|
||||
pytest --cov=src --cov-report=html
|
||||
|
||||
# Run specific test file
|
||||
pytest tests/test_models.py
|
||||
|
||||
# Run with verbose output
|
||||
pytest -v
|
||||
|
||||
# Run tests matching pattern
|
||||
pytest -k "test_user"
|
||||
```
|
||||
|
||||
### unittest
|
||||
```bash
|
||||
# Run all tests
|
||||
python -m unittest discover
|
||||
|
||||
# Run specific test file
|
||||
python -m unittest tests.test_models
|
||||
|
||||
# Run with verbose output
|
||||
python -m unittest -v
|
||||
```
|
||||
|
||||
### Django tests
|
||||
```bash
|
||||
# Run all Django tests
|
||||
python manage.py test
|
||||
|
||||
# Run specific app tests
|
||||
python manage.py test myapp
|
||||
|
||||
# Run with coverage
|
||||
coverage run --source='.' manage.py test
|
||||
coverage report
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
- Write tests for all critical functionality
|
||||
- Use descriptive test names
|
||||
- Keep tests isolated and independent
|
||||
- Mock external dependencies
|
||||
- Aim for high test coverage (80%+)
|
||||
@@ -0,0 +1,153 @@
|
||||
{
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"Bash",
|
||||
"Edit",
|
||||
"MultiEdit",
|
||||
"Write",
|
||||
"Bash(python:*)",
|
||||
"Bash(pip:*)",
|
||||
"Bash(pytest:*)",
|
||||
"Bash(black:*)",
|
||||
"Bash(isort:*)",
|
||||
"Bash(flake8:*)",
|
||||
"Bash(mypy:*)",
|
||||
"Bash(django-admin:*)",
|
||||
"Bash(flask:*)",
|
||||
"Bash(uvicorn:*)",
|
||||
"Bash(gunicorn:*)",
|
||||
"Bash(git:*)"
|
||||
],
|
||||
"deny": [
|
||||
"Bash(curl:*)",
|
||||
"Bash(wget:*)",
|
||||
"Bash(rm -rf:*)"
|
||||
],
|
||||
"defaultMode": "allowEdits"
|
||||
},
|
||||
"env": {
|
||||
"BASH_DEFAULT_TIMEOUT_MS": "60000",
|
||||
"BASH_MAX_OUTPUT_LENGTH": "20000",
|
||||
"CLAUDE_BASH_MAINTAIN_PROJECT_WORKING_DIR": "1",
|
||||
"PYTHONPATH": "."
|
||||
},
|
||||
"includeCoAuthoredBy": true,
|
||||
"cleanupPeriodDays": 30,
|
||||
"hooks": {
|
||||
"PreToolUse": [
|
||||
{
|
||||
"matcher": "Bash",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "jq -r '\"\\(.tool_input.command) - \\(.tool_input.description // \"No description\")\"' >> ~/.claude/bash-command-log.txt"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"matcher": "Write",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "FILE=$(echo $STDIN_JSON | jq -r '.tool_input.file_path // \"\"); CONTENT=$(echo $STDIN_JSON | jq -r '.tool_input.content // \"\"); if [[ \"$FILE\" =~ \\.py$ ]] && echo \"$CONTENT\" | grep -q 'print('; then echo 'Warning: print() statements should be replaced with logging' >&2; exit 2; fi"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"matcher": "Write",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "FILE=$(echo $STDIN_JSON | jq -r '.tool_input.file_path // \"\"'); if [[ \"$FILE\" == \"requirements.txt\" ]] || [[ \"$FILE\" == \"pyproject.toml\" ]] || [[ \"$FILE\" == \"setup.py\" ]]; then echo 'Checking for vulnerable dependencies...'; if command -v safety >/dev/null 2>&1; then safety check; elif command -v pip-audit >/dev/null 2>&1; then pip-audit; else echo 'No security audit tool found. Install safety or pip-audit'; fi; fi",
|
||||
"timeout": 60
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"PostToolUse": [
|
||||
{
|
||||
"matcher": "Write|Edit|MultiEdit",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "FILE=$(echo $STDIN_JSON | jq -r '.tool_input.file_path // \"\"'); if [[ \"$FILE\" =~ \\.py$ ]]; then black \"$FILE\" 2>/dev/null || echo 'Black formatting skipped (not installed)'; fi",
|
||||
"timeout": 30
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"matcher": "Write|Edit|MultiEdit",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "FILE=$(echo $STDIN_JSON | jq -r '.tool_input.file_path // \"\"'); if [[ \"$FILE\" =~ \\.py$ ]]; then isort \"$FILE\" 2>/dev/null || echo 'isort skipped (not installed)'; fi",
|
||||
"timeout": 30
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"matcher": "Write|Edit|MultiEdit",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "FILE=$(echo $STDIN_JSON | jq -r '.tool_input.file_path // \"\"'); if [[ \"$FILE\" =~ \\.py$ ]]; then RESULT=$(flake8 \"$FILE\" 2>&1); if [ $? -ne 0 ] && command -v flake8 >/dev/null 2>&1; then echo \"Flake8 linting issues found: $RESULT\" >&2; exit 2; fi; fi",
|
||||
"timeout": 30
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"matcher": "Write|Edit|MultiEdit",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "FILE=$(echo $STDIN_JSON | jq -r '.tool_input.file_path // \"\"'); if [[ \"$FILE\" =~ \\.py$ ]]; then RESULT=$(mypy \"$FILE\" 2>&1); if [ $? -ne 0 ] && command -v mypy >/dev/null 2>&1; then echo \"MyPy type checking issues found: $RESULT\" >&2; exit 2; fi; fi",
|
||||
"timeout": 30
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"matcher": "Write|Edit|MultiEdit",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "FILE=$(echo $STDIN_JSON | jq -r '.tool_input.file_path // \"\"'); if [[ \"$FILE\" =~ \\.py$ && \"$FILE\" != *\"test_\"* && \"$FILE\" != *\"_test.py\" ]]; then DIR=$(dirname \"$FILE\"); BASENAME=$(basename \"$FILE\" .py); for TEST_FILE in \"$DIR/test_$BASENAME.py\" \"$DIR/${BASENAME}_test.py\" \"tests/test_$BASENAME.py\"; do if [ -f \"$TEST_FILE\" ]; then echo \"Running tests for $TEST_FILE...\"; if command -v pytest >/dev/null 2>&1; then pytest \"$TEST_FILE\" -v; elif python -m pytest \"$TEST_FILE\" 2>/dev/null; then python -m pytest \"$TEST_FILE\" -v; else python -m unittest \"$TEST_FILE\" 2>/dev/null || echo 'No test runner found'; fi; break; fi; done; fi",
|
||||
"timeout": 60
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"Notification": [
|
||||
{
|
||||
"matcher": "",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "echo \"Claude Code notification: $(date)\" >> ~/.claude/notifications.log"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"Stop": [
|
||||
{
|
||||
"matcher": "",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "if [[ -f requirements.txt || -f pyproject.toml || -f setup.py ]] && [[ $(git status --porcelain | grep '\\.py$') ]]; then echo 'Running linter on changed Python files...'; if command -v flake8 >/dev/null 2>&1; then flake8 $(git diff --name-only --diff-filter=ACMR | grep '\\.py$'); elif command -v pylint >/dev/null 2>&1; then pylint $(git diff --name-only --diff-filter=ACMR | grep '\\.py$'); else echo 'No Python linter found (flake8/pylint)'; fi; fi",
|
||||
"timeout": 60
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"matcher": "",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "if [[ -f requirements.txt || -f pyproject.toml || -f setup.py ]] && [[ $(git status --porcelain | grep '\\.py$') ]]; then echo 'Running type checking on changed files...'; if command -v mypy >/dev/null 2>&1; then mypy $(git diff --name-only --diff-filter=ACMR | grep '\\.py$') || echo 'Type checking completed with issues'; else echo 'MyPy not found for type checking'; fi; fi",
|
||||
"timeout": 60
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
{
|
||||
"mcpServers": {
|
||||
"python-sdk": {
|
||||
"name": "Python SDK",
|
||||
"description": "Official Python SDK with FastMCP for rapid MCP development",
|
||||
"command": "python",
|
||||
"args": ["-m", "python_sdk.server"],
|
||||
"env": {}
|
||||
},
|
||||
"docker": {
|
||||
"name": "Docker MCP",
|
||||
"description": "Isolated code execution via Docker containers",
|
||||
"command": "python",
|
||||
"args": ["-m", "mcp_server_docker"],
|
||||
"env": {}
|
||||
},
|
||||
"jupyter": {
|
||||
"name": "Jupyter MCP",
|
||||
"description": "MCP integration for interactive Jupyter notebooks",
|
||||
"command": "python",
|
||||
"args": ["-m", "server_jupyter"],
|
||||
"env": {}
|
||||
},
|
||||
"postgresql": {
|
||||
"name": "PostgreSQL MCP",
|
||||
"description": "Natural language queries to PostgreSQL databases",
|
||||
"command": "python",
|
||||
"args": ["-m", "server_postgres"],
|
||||
"env": {
|
||||
"DATABASE_URL": "postgresql://user:pass@host/db"
|
||||
}
|
||||
},
|
||||
"opik": {
|
||||
"name": "Opik MCP",
|
||||
"description": "Observability for LLM apps with tracing and metrics",
|
||||
"command": "python",
|
||||
"args": ["-m", "opik_mcp"],
|
||||
"env": {}
|
||||
},
|
||||
"memory-bank": {
|
||||
"name": "Memory Bank MCP",
|
||||
"description": "Centralized memory system for AI agents",
|
||||
"command": "server-memory",
|
||||
"args": [],
|
||||
"env": {}
|
||||
},
|
||||
"sequential-thinking": {
|
||||
"name": "Sequential Thinking MCP",
|
||||
"description": "Helps LLMs decompose complex tasks into logical steps",
|
||||
"command": "code-reasoning",
|
||||
"args": [],
|
||||
"env": {}
|
||||
},
|
||||
"brave-search": {
|
||||
"name": "Brave Search MCP",
|
||||
"description": "Privacy-focused web search tool",
|
||||
"command": "server-brave-search",
|
||||
"args": [],
|
||||
"env": {}
|
||||
},
|
||||
"google-maps": {
|
||||
"name": "Google Maps MCP",
|
||||
"description": "Integrates Google Maps for geolocation and directions",
|
||||
"command": "server-google-maps",
|
||||
"args": [],
|
||||
"env": {
|
||||
"GOOGLE_MAPS_API_KEY": "..."
|
||||
}
|
||||
},
|
||||
"deep-graph": {
|
||||
"name": "Deep Graph MCP (Code Graph)",
|
||||
"description": "Transforms source code into semantic graphs via DeepGraph",
|
||||
"command": "mcp-code-graph",
|
||||
"args": [],
|
||||
"env": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,276 @@
|
||||
# CLAUDE.md
|
||||
|
||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||
|
||||
## Project Overview
|
||||
|
||||
This is a Python project optimized for modern Python development. The project uses industry-standard tools and follows best practices for scalable application development.
|
||||
|
||||
## Development Commands
|
||||
|
||||
### Environment Management
|
||||
- `python -m venv venv` - Create virtual environment
|
||||
- `source venv/bin/activate` (Linux/Mac) or `venv\Scripts\activate` (Windows) - Activate virtual environment
|
||||
- `deactivate` - Deactivate virtual environment
|
||||
- `pip install -r requirements.txt` - Install dependencies
|
||||
- `pip install -r requirements-dev.txt` - Install development dependencies
|
||||
|
||||
### Package Management
|
||||
- `pip install <package>` - Install a package
|
||||
- `pip install -e .` - Install project in development mode
|
||||
- `pip freeze > requirements.txt` - Generate requirements file
|
||||
- `pip-tools compile requirements.in` - Compile requirements with pip-tools
|
||||
|
||||
### Testing Commands
|
||||
- `pytest` - Run all tests
|
||||
- `pytest -v` - Run tests with verbose output
|
||||
- `pytest --cov` - Run tests with coverage report
|
||||
- `pytest --cov-report=html` - Generate HTML coverage report
|
||||
- `pytest -x` - Stop on first failure
|
||||
- `pytest -k "test_name"` - Run specific test by name
|
||||
- `python -m unittest` - Run tests with unittest
|
||||
|
||||
### Code Quality Commands
|
||||
- `black .` - Format code with Black
|
||||
- `black --check .` - Check code formatting without changes
|
||||
- `isort .` - Sort imports
|
||||
- `isort --check-only .` - Check import sorting
|
||||
- `flake8` - Run linting with Flake8
|
||||
- `pylint src/` - Run linting with Pylint
|
||||
- `mypy src/` - Run type checking with MyPy
|
||||
|
||||
### Development Tools
|
||||
- `python -m pip install --upgrade pip` - Upgrade pip
|
||||
- `python -c "import sys; print(sys.version)"` - Check Python version
|
||||
- `python -m site` - Show Python site information
|
||||
- `python -m pdb script.py` - Debug with pdb
|
||||
|
||||
## Technology Stack
|
||||
|
||||
### Core Technologies
|
||||
- **Python** - Primary programming language (3.8+)
|
||||
- **pip** - Package management
|
||||
- **venv** - Virtual environment management
|
||||
|
||||
### Common Frameworks
|
||||
- **Django** - High-level web framework
|
||||
- **Flask** - Micro web framework
|
||||
- **FastAPI** - Modern API framework with automatic documentation
|
||||
- **SQLAlchemy** - SQL toolkit and ORM
|
||||
- **Pydantic** - Data validation using Python type hints
|
||||
|
||||
### Data Science & ML
|
||||
- **NumPy** - Numerical computing
|
||||
- **Pandas** - Data manipulation and analysis
|
||||
- **Matplotlib/Seaborn** - Data visualization
|
||||
- **Scikit-learn** - Machine learning library
|
||||
- **TensorFlow/PyTorch** - Deep learning frameworks
|
||||
|
||||
### Testing Frameworks
|
||||
- **pytest** - Testing framework
|
||||
- **unittest** - Built-in testing framework
|
||||
- **pytest-cov** - Coverage plugin for pytest
|
||||
- **factory-boy** - Test fixtures
|
||||
- **responses** - Mock HTTP requests
|
||||
|
||||
### Code Quality Tools
|
||||
- **Black** - Code formatter
|
||||
- **isort** - Import sorter
|
||||
- **flake8** - Style guide enforcement
|
||||
- **pylint** - Code analysis
|
||||
- **mypy** - Static type checker
|
||||
- **pre-commit** - Git hooks framework
|
||||
|
||||
## Project Structure Guidelines
|
||||
|
||||
### File Organization
|
||||
```
|
||||
src/
|
||||
├── package_name/
|
||||
│ ├── __init__.py
|
||||
│ ├── main.py # Application entry point
|
||||
│ ├── models/ # Data models
|
||||
│ ├── views/ # Web views (Django/Flask)
|
||||
│ ├── api/ # API endpoints
|
||||
│ ├── services/ # Business logic
|
||||
│ ├── utils/ # Utility functions
|
||||
│ └── config/ # Configuration files
|
||||
tests/
|
||||
├── __init__.py
|
||||
├── conftest.py # pytest configuration
|
||||
├── test_models.py
|
||||
├── test_views.py
|
||||
└── test_utils.py
|
||||
requirements/
|
||||
├── base.txt # Base requirements
|
||||
├── dev.txt # Development requirements
|
||||
└── prod.txt # Production requirements
|
||||
```
|
||||
|
||||
### Naming Conventions
|
||||
- **Files/Modules**: Use snake_case (`user_profile.py`)
|
||||
- **Classes**: Use PascalCase (`UserProfile`)
|
||||
- **Functions/Variables**: Use snake_case (`get_user_data`)
|
||||
- **Constants**: Use UPPER_SNAKE_CASE (`API_BASE_URL`)
|
||||
- **Private methods**: Prefix with underscore (`_private_method`)
|
||||
|
||||
## Python Guidelines
|
||||
|
||||
### Type Hints
|
||||
- Use type hints for function parameters and return values
|
||||
- Import types from `typing` module when needed
|
||||
- Use `Optional` for nullable values
|
||||
- Use `Union` for multiple possible types
|
||||
- Document complex types with comments
|
||||
|
||||
### Code Style
|
||||
- Follow PEP 8 style guide
|
||||
- Use meaningful variable and function names
|
||||
- Keep functions focused and single-purpose
|
||||
- Use docstrings for modules, classes, and functions
|
||||
- Limit line length to 88 characters (Black default)
|
||||
|
||||
### Best Practices
|
||||
- Use list comprehensions for simple transformations
|
||||
- Prefer `pathlib` over `os.path` for file operations
|
||||
- Use context managers (`with` statements) for resource management
|
||||
- Handle exceptions appropriately with try/except blocks
|
||||
- Use `logging` module instead of print statements
|
||||
|
||||
## Testing Standards
|
||||
|
||||
### Test Structure
|
||||
- Organize tests to mirror source code structure
|
||||
- Use descriptive test names that explain the behavior
|
||||
- Follow AAA pattern (Arrange, Act, Assert)
|
||||
- Use fixtures for common test data
|
||||
- Group related tests in classes
|
||||
|
||||
### Coverage Goals
|
||||
- Aim for 90%+ test coverage
|
||||
- Write unit tests for business logic
|
||||
- Use integration tests for external dependencies
|
||||
- Mock external services in tests
|
||||
- Test error conditions and edge cases
|
||||
|
||||
### pytest Configuration
|
||||
```python
|
||||
# pytest.ini or pyproject.toml
|
||||
[tool.pytest.ini_options]
|
||||
testpaths = ["tests"]
|
||||
python_files = ["test_*.py", "*_test.py"]
|
||||
python_classes = ["Test*"]
|
||||
python_functions = ["test_*"]
|
||||
addopts = "--cov=src --cov-report=term-missing"
|
||||
```
|
||||
|
||||
## Virtual Environment Setup
|
||||
|
||||
### Creation and Activation
|
||||
```bash
|
||||
# Create virtual environment
|
||||
python -m venv venv
|
||||
|
||||
# Activate (Linux/Mac)
|
||||
source venv/bin/activate
|
||||
|
||||
# Activate (Windows)
|
||||
venv\Scripts\activate
|
||||
|
||||
# Install dependencies
|
||||
pip install -r requirements.txt
|
||||
pip install -r requirements-dev.txt
|
||||
```
|
||||
|
||||
### Requirements Management
|
||||
- Use `requirements.txt` for production dependencies
|
||||
- Use `requirements-dev.txt` for development dependencies
|
||||
- Consider using `pip-tools` for dependency resolution
|
||||
- Pin versions for reproducible builds
|
||||
|
||||
## Django-Specific Guidelines
|
||||
|
||||
### Project Structure
|
||||
```
|
||||
project_name/
|
||||
├── manage.py
|
||||
├── project_name/
|
||||
│ ├── __init__.py
|
||||
│ ├── settings/
|
||||
│ ├── urls.py
|
||||
│ └── wsgi.py
|
||||
├── apps/
|
||||
│ ├── users/
|
||||
│ ├── products/
|
||||
│ └── orders/
|
||||
└── requirements/
|
||||
```
|
||||
|
||||
### Common Commands
|
||||
- `python manage.py runserver` - Start development server
|
||||
- `python manage.py migrate` - Apply database migrations
|
||||
- `python manage.py makemigrations` - Create new migrations
|
||||
- `python manage.py createsuperuser` - Create admin user
|
||||
- `python manage.py collectstatic` - Collect static files
|
||||
- `python manage.py test` - Run Django tests
|
||||
|
||||
## FastAPI-Specific Guidelines
|
||||
|
||||
### Project Structure
|
||||
```
|
||||
src/
|
||||
├── main.py # FastAPI application
|
||||
├── api/
|
||||
│ ├── __init__.py
|
||||
│ ├── dependencies.py # Dependency injection
|
||||
│ └── v1/
|
||||
│ ├── __init__.py
|
||||
│ └── endpoints/
|
||||
├── core/
|
||||
│ ├── __init__.py
|
||||
│ ├── config.py # Settings
|
||||
│ └── security.py # Authentication
|
||||
├── models/
|
||||
├── schemas/ # Pydantic models
|
||||
└── services/
|
||||
```
|
||||
|
||||
### Common Commands
|
||||
- `uvicorn main:app --reload` - Start development server
|
||||
- `uvicorn main:app --host 0.0.0.0 --port 8000` - Start production server
|
||||
|
||||
## Security Guidelines
|
||||
|
||||
### Dependencies
|
||||
- Regularly update dependencies with `pip list --outdated`
|
||||
- Use `safety` package to check for known vulnerabilities
|
||||
- Pin dependency versions in requirements files
|
||||
- Use virtual environments to isolate dependencies
|
||||
|
||||
### Code Security
|
||||
- Validate input data with Pydantic or similar
|
||||
- Use environment variables for sensitive configuration
|
||||
- Implement proper authentication and authorization
|
||||
- Sanitize data before database operations
|
||||
- Use HTTPS for production deployments
|
||||
|
||||
## Development Workflow
|
||||
|
||||
### Before Starting
|
||||
1. Check Python version compatibility
|
||||
2. Create and activate virtual environment
|
||||
3. Install dependencies from requirements files
|
||||
4. Run type checking with `mypy`
|
||||
|
||||
### During Development
|
||||
1. Use type hints for better code documentation
|
||||
2. Run tests frequently to catch issues early
|
||||
3. Use meaningful commit messages
|
||||
4. Format code with Black before committing
|
||||
|
||||
### Before Committing
|
||||
1. Run full test suite: `pytest`
|
||||
2. Check code formatting: `black --check .`
|
||||
3. Sort imports: `isort --check-only .`
|
||||
4. Run linting: `flake8`
|
||||
5. Run type checking: `mypy src/`
|
||||
@@ -0,0 +1,264 @@
|
||||
# Django Admin Configuration
|
||||
|
||||
Configure Django admin interface with custom admin classes and functionality.
|
||||
|
||||
## Purpose
|
||||
|
||||
This command helps you create comprehensive Django admin configurations with advanced features and customizations.
|
||||
|
||||
## Usage
|
||||
|
||||
```
|
||||
/admin
|
||||
```
|
||||
|
||||
## What this command does
|
||||
|
||||
1. **Registers models** with custom admin classes
|
||||
2. **Creates advanced admin interfaces** with filtering, search, and actions
|
||||
3. **Adds inline editing** for related models
|
||||
4. **Customizes list displays** and forms
|
||||
5. **Implements admin actions** for bulk operations
|
||||
|
||||
## Example Output
|
||||
|
||||
```python
|
||||
# admin.py
|
||||
from django.contrib import admin
|
||||
from django.utils.html import format_html
|
||||
from django.urls import reverse
|
||||
from django.utils.safestring import mark_safe
|
||||
from .models import Post, Category, Tag, Comment
|
||||
|
||||
@admin.register(Category)
|
||||
class CategoryAdmin(admin.ModelAdmin):
|
||||
"""Admin configuration for Category model."""
|
||||
list_display = ['name', 'slug', 'post_count', 'created_at']
|
||||
list_filter = ['created_at']
|
||||
search_fields = ['name', 'description']
|
||||
prepopulated_fields = {'slug': ('name',)}
|
||||
readonly_fields = ['created_at', 'updated_at']
|
||||
|
||||
def post_count(self, obj):
|
||||
"""Display number of posts in this category."""
|
||||
count = obj.posts.count()
|
||||
url = reverse('admin:blog_post_changelist') + f'?category__id__exact={obj.id}'
|
||||
return format_html('<a href="{}">{} posts</a>', url, count)
|
||||
post_count.short_description = 'Posts'
|
||||
|
||||
class CommentInline(admin.TabularInline):
|
||||
"""Inline admin for comments."""
|
||||
model = Comment
|
||||
extra = 0
|
||||
readonly_fields = ['created_at', 'author']
|
||||
fields = ['author', 'content', 'is_approved', 'created_at']
|
||||
|
||||
@admin.register(Post)
|
||||
class PostAdmin(admin.ModelAdmin):
|
||||
"""Advanced admin configuration for Post model."""
|
||||
list_display = [
|
||||
'title',
|
||||
'author',
|
||||
'category',
|
||||
'status',
|
||||
'view_count',
|
||||
'created_at',
|
||||
'post_preview'
|
||||
]
|
||||
list_filter = [
|
||||
'status',
|
||||
'category',
|
||||
'created_at',
|
||||
'updated_at',
|
||||
('author', admin.RelatedOnlyFieldListFilter)
|
||||
]
|
||||
search_fields = ['title', 'content', 'author__username']
|
||||
prepopulated_fields = {'slug': ('title',)}
|
||||
readonly_fields = ['created_at', 'updated_at', 'view_count', 'post_preview']
|
||||
|
||||
# Custom form layout
|
||||
fieldsets = (
|
||||
('Content', {
|
||||
'fields': ('title', 'slug', 'content', 'status')
|
||||
}),
|
||||
('Metadata', {
|
||||
'fields': ('author', 'category', 'tags'),
|
||||
'classes': ('collapse',)
|
||||
}),
|
||||
('SEO', {
|
||||
'fields': ('meta_description', 'meta_keywords'),
|
||||
'classes': ('collapse',)
|
||||
}),
|
||||
('Timestamps', {
|
||||
'fields': ('created_at', 'updated_at', 'view_count'),
|
||||
'classes': ('collapse',)
|
||||
}),
|
||||
)
|
||||
|
||||
# Many-to-many field display
|
||||
filter_horizontal = ['tags']
|
||||
|
||||
# Inline models
|
||||
inlines = [CommentInline]
|
||||
|
||||
# Custom list display methods
|
||||
def post_preview(self, obj):
|
||||
"""Show thumbnail preview of post."""
|
||||
if obj.featured_image:
|
||||
return format_html(
|
||||
'<img src="{}" width="50" height="50" style="border-radius: 5px;" />',
|
||||
obj.featured_image.url
|
||||
)
|
||||
return "No image"
|
||||
post_preview.short_description = 'Preview'
|
||||
|
||||
# Custom admin actions
|
||||
actions = ['make_published', 'make_draft', 'duplicate_posts']
|
||||
|
||||
def make_published(self, request, queryset):
|
||||
"""Bulk action to publish selected posts."""
|
||||
updated = queryset.update(status='published')
|
||||
self.message_user(
|
||||
request,
|
||||
f'{updated} posts were successfully marked as published.'
|
||||
)
|
||||
make_published.short_description = "Mark selected posts as published"
|
||||
|
||||
def make_draft(self, request, queryset):
|
||||
"""Bulk action to set selected posts as draft."""
|
||||
updated = queryset.update(status='draft')
|
||||
self.message_user(
|
||||
request,
|
||||
f'{updated} posts were successfully marked as draft.'
|
||||
)
|
||||
make_draft.short_description = "Mark selected posts as draft"
|
||||
|
||||
def duplicate_posts(self, request, queryset):
|
||||
"""Bulk action to duplicate selected posts."""
|
||||
count = 0
|
||||
for post in queryset:
|
||||
post.pk = None # Create new instance
|
||||
post.title = f"Copy of {post.title}"
|
||||
post.slug = f"copy-of-{post.slug}"
|
||||
post.status = 'draft'
|
||||
post.save()
|
||||
count += 1
|
||||
|
||||
self.message_user(
|
||||
request,
|
||||
f'{count} posts were successfully duplicated.'
|
||||
)
|
||||
duplicate_posts.short_description = "Duplicate selected posts"
|
||||
|
||||
@admin.register(Tag)
|
||||
class TagAdmin(admin.ModelAdmin):
|
||||
"""Admin configuration for Tag model."""
|
||||
list_display = ['name', 'slug', 'post_count', 'color_preview']
|
||||
search_fields = ['name']
|
||||
prepopulated_fields = {'slug': ('name',)}
|
||||
|
||||
def post_count(self, obj):
|
||||
"""Display number of posts with this tag."""
|
||||
return obj.posts.count()
|
||||
post_count.short_description = 'Posts'
|
||||
|
||||
def color_preview(self, obj):
|
||||
"""Show color preview if tag has color field."""
|
||||
if hasattr(obj, 'color') and obj.color:
|
||||
return format_html(
|
||||
'<span style="background-color: {}; padding: 2px 8px; border-radius: 3px; color: white;">{}</span>',
|
||||
obj.color,
|
||||
obj.name
|
||||
)
|
||||
return obj.name
|
||||
color_preview.short_description = 'Preview'
|
||||
|
||||
@admin.register(Comment)
|
||||
class CommentAdmin(admin.ModelAdmin):
|
||||
"""Admin configuration for Comment model."""
|
||||
list_display = ['author', 'post', 'content_preview', 'is_approved', 'created_at']
|
||||
list_filter = ['is_approved', 'created_at', 'post__category']
|
||||
search_fields = ['content', 'author__username', 'post__title']
|
||||
readonly_fields = ['created_at', 'updated_at']
|
||||
actions = ['approve_comments', 'disapprove_comments']
|
||||
|
||||
def content_preview(self, obj):
|
||||
"""Show truncated content preview."""
|
||||
return obj.content[:50] + "..." if len(obj.content) > 50 else obj.content
|
||||
content_preview.short_description = 'Content'
|
||||
|
||||
def approve_comments(self, request, queryset):
|
||||
"""Bulk approve comments."""
|
||||
updated = queryset.update(is_approved=True)
|
||||
self.message_user(request, f'{updated} comments were approved.')
|
||||
approve_comments.short_description = "Approve selected comments"
|
||||
|
||||
def disapprove_comments(self, request, queryset):
|
||||
"""Bulk disapprove comments."""
|
||||
updated = queryset.update(is_approved=False)
|
||||
self.message_user(request, f'{updated} comments were disapproved.')
|
||||
disapprove_comments.short_description = "Disapprove selected comments"
|
||||
|
||||
# Custom admin site configuration
|
||||
admin.site.site_header = "Blog Administration"
|
||||
admin.site.site_title = "Blog Admin Portal"
|
||||
admin.site.index_title = "Welcome to Blog Administration"
|
||||
```
|
||||
|
||||
## Advanced Features
|
||||
|
||||
### Custom List Display
|
||||
- **Thumbnail previews** for images
|
||||
- **Related object counts** with links
|
||||
- **Status indicators** with colors
|
||||
- **Custom formatting** for data display
|
||||
|
||||
### Filtering and Search
|
||||
- **Advanced filters** including date ranges
|
||||
- **Related field filtering** for foreign keys
|
||||
- **Search across multiple fields** including relations
|
||||
- **Custom filter classes** for complex queries
|
||||
|
||||
### Inline Editing
|
||||
- **TabularInline** for compact editing
|
||||
- **StackedInline** for detailed forms
|
||||
- **Custom inline forms** with additional functionality
|
||||
- **Readonly fields** in inlines
|
||||
|
||||
### Bulk Actions
|
||||
- **Status changes** for multiple objects
|
||||
- **Data export** functionality
|
||||
- **Batch operations** for efficiency
|
||||
- **Custom business logic** in actions
|
||||
|
||||
### Form Customization
|
||||
- **Fieldsets** for organized layouts
|
||||
- **Collapsed sections** for advanced options
|
||||
- **Custom widgets** for better UX
|
||||
- **Validation** and error handling
|
||||
|
||||
## Security Considerations
|
||||
|
||||
- **Permission checks** in custom methods
|
||||
- **Input sanitization** in admin actions
|
||||
- **CSRF protection** (automatic)
|
||||
- **User authentication** (built-in)
|
||||
|
||||
## Performance Optimization
|
||||
|
||||
```python
|
||||
# Optimize queries with select_related/prefetch_related
|
||||
class PostAdmin(admin.ModelAdmin):
|
||||
def get_queryset(self, request):
|
||||
queryset = super().get_queryset(request)
|
||||
return queryset.select_related('author', 'category').prefetch_related('tags')
|
||||
```
|
||||
|
||||
## Custom Admin Templates
|
||||
|
||||
```python
|
||||
# Override admin templates
|
||||
class PostAdmin(admin.ModelAdmin):
|
||||
change_form_template = 'admin/blog/post/change_form.html'
|
||||
change_list_template = 'admin/blog/post/change_list.html'
|
||||
```
|
||||
@@ -0,0 +1,124 @@
|
||||
# Django Model Generator
|
||||
|
||||
Create Django models with proper structure and relationships.
|
||||
|
||||
## Purpose
|
||||
|
||||
This command helps you quickly create Django models with fields, relationships, and best practices.
|
||||
|
||||
## Usage
|
||||
|
||||
```
|
||||
/django-model
|
||||
```
|
||||
|
||||
## What this command does
|
||||
|
||||
1. **Creates model classes** with proper field definitions
|
||||
2. **Adds relationships** (ForeignKey, ManyToMany, OneToOne)
|
||||
3. **Includes meta options** and model methods
|
||||
4. **Generates migrations** automatically
|
||||
5. **Follows Django conventions** and best practices
|
||||
|
||||
## Example Output
|
||||
|
||||
```python
|
||||
# models.py
|
||||
from django.db import models
|
||||
from django.contrib.auth.models import User
|
||||
|
||||
class Category(models.Model):
|
||||
name = models.CharField(max_length=100, unique=True)
|
||||
slug = models.SlugField(unique=True)
|
||||
description = models.TextField(blank=True)
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
updated_at = models.DateTimeField(auto_now=True)
|
||||
|
||||
class Meta:
|
||||
verbose_name_plural = "categories"
|
||||
ordering = ['name']
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
class Post(models.Model):
|
||||
STATUS_CHOICES = [
|
||||
('draft', 'Draft'),
|
||||
('published', 'Published'),
|
||||
('archived', 'Archived'),
|
||||
]
|
||||
|
||||
title = models.CharField(max_length=200)
|
||||
slug = models.SlugField(unique=True)
|
||||
content = models.TextField()
|
||||
status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='draft')
|
||||
author = models.ForeignKey(User, on_delete=models.CASCADE, related_name='posts')
|
||||
category = models.ForeignKey(Category, on_delete=models.SET_NULL, null=True, blank=True)
|
||||
tags = models.ManyToManyField('Tag', blank=True)
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
updated_at = models.DateTimeField(auto_now=True)
|
||||
|
||||
class Meta:
|
||||
ordering = ['-created_at']
|
||||
verbose_name = 'blog post'
|
||||
verbose_name_plural = 'blog posts'
|
||||
|
||||
def __str__(self):
|
||||
return self.title
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse('post_detail', kwargs={'slug': self.slug})
|
||||
|
||||
class Tag(models.Model):
|
||||
name = models.CharField(max_length=50, unique=True)
|
||||
slug = models.SlugField(unique=True)
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
```
|
||||
|
||||
## Field Types Supported
|
||||
|
||||
- **CharField** - Text fields with max length
|
||||
- **TextField** - Large text fields
|
||||
- **IntegerField** - Integer numbers
|
||||
- **FloatField** - Floating point numbers
|
||||
- **BooleanField** - True/False values
|
||||
- **DateField** - Date only
|
||||
- **DateTimeField** - Date and time
|
||||
- **EmailField** - Email addresses
|
||||
- **URLField** - URLs
|
||||
- **ImageField** - Image uploads
|
||||
- **FileField** - File uploads
|
||||
- **JSONField** - JSON data (PostgreSQL)
|
||||
|
||||
## Relationships
|
||||
|
||||
- **ForeignKey** - One-to-many relationships
|
||||
- **ManyToManyField** - Many-to-many relationships
|
||||
- **OneToOneField** - One-to-one relationships
|
||||
|
||||
## Best Practices Included
|
||||
|
||||
- Proper field choices and defaults
|
||||
- Appropriate related_name attributes
|
||||
- __str__ methods for admin interface
|
||||
- Meta class with ordering and verbose names
|
||||
- get_absolute_url methods where appropriate
|
||||
- Proper use of null and blank parameters
|
||||
- Field validation and constraints
|
||||
|
||||
## After Creating Models
|
||||
|
||||
```bash
|
||||
# Create and apply migrations
|
||||
python manage.py makemigrations
|
||||
python manage.py migrate
|
||||
|
||||
# Register in admin (optional)
|
||||
# Add to admin.py:
|
||||
from django.contrib import admin
|
||||
from .models import Post, Category, Tag
|
||||
|
||||
admin.site.register([Post, Category, Tag])
|
||||
```
|
||||
@@ -0,0 +1,222 @@
|
||||
# Django Views Generator
|
||||
|
||||
Create Django views with proper structure and best practices.
|
||||
|
||||
## Purpose
|
||||
|
||||
This command helps you quickly create Django views (Function-Based Views and Class-Based Views) following Django conventions.
|
||||
|
||||
## Usage
|
||||
|
||||
```
|
||||
/views
|
||||
```
|
||||
|
||||
## What this command does
|
||||
|
||||
1. **Creates view functions/classes** with proper structure
|
||||
2. **Handles HTTP methods** (GET, POST, PUT, DELETE)
|
||||
3. **Includes form handling** and validation
|
||||
4. **Adds authentication/authorization** checks
|
||||
5. **Follows Django best practices** and security guidelines
|
||||
|
||||
## Example Output
|
||||
|
||||
```python
|
||||
# views.py
|
||||
from django.shortcuts import render, get_object_or_404, redirect
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.contrib import messages
|
||||
from django.http import JsonResponse
|
||||
from django.views.generic import ListView, DetailView, CreateView, UpdateView, DeleteView
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
from django.urls import reverse_lazy
|
||||
from .models import Post, Category
|
||||
from .forms import PostForm
|
||||
|
||||
# Function-Based Views
|
||||
def post_list(request):
|
||||
"""Display list of posts with pagination and filtering."""
|
||||
posts = Post.objects.filter(status='published').select_related('author', 'category')
|
||||
|
||||
# Search functionality
|
||||
search_query = request.GET.get('search')
|
||||
if search_query:
|
||||
posts = posts.filter(title__icontains=search_query)
|
||||
|
||||
# Category filtering
|
||||
category_id = request.GET.get('category')
|
||||
if category_id:
|
||||
posts = posts.filter(category_id=category_id)
|
||||
|
||||
context = {
|
||||
'posts': posts,
|
||||
'categories': Category.objects.all(),
|
||||
'search_query': search_query,
|
||||
}
|
||||
return render(request, 'blog/post_list.html', context)
|
||||
|
||||
def post_detail(request, slug):
|
||||
"""Display individual post details."""
|
||||
post = get_object_or_404(Post, slug=slug, status='published')
|
||||
|
||||
context = {
|
||||
'post': post,
|
||||
'related_posts': Post.objects.filter(
|
||||
category=post.category,
|
||||
status='published'
|
||||
).exclude(id=post.id)[:3]
|
||||
}
|
||||
return render(request, 'blog/post_detail.html', context)
|
||||
|
||||
@login_required
|
||||
def post_create(request):
|
||||
"""Create new post."""
|
||||
if request.method == 'POST':
|
||||
form = PostForm(request.POST, request.FILES)
|
||||
if form.is_valid():
|
||||
post = form.save(commit=False)
|
||||
post.author = request.user
|
||||
post.save()
|
||||
form.save_m2m() # Save many-to-many relationships
|
||||
messages.success(request, 'Post created successfully!')
|
||||
return redirect('post_detail', slug=post.slug)
|
||||
else:
|
||||
form = PostForm()
|
||||
|
||||
return render(request, 'blog/post_form.html', {'form': form})
|
||||
|
||||
@login_required
|
||||
def post_edit(request, slug):
|
||||
"""Edit existing post."""
|
||||
post = get_object_or_404(Post, slug=slug, author=request.user)
|
||||
|
||||
if request.method == 'POST':
|
||||
form = PostForm(request.POST, request.FILES, instance=post)
|
||||
if form.is_valid():
|
||||
form.save()
|
||||
messages.success(request, 'Post updated successfully!')
|
||||
return redirect('post_detail', slug=post.slug)
|
||||
else:
|
||||
form = PostForm(instance=post)
|
||||
|
||||
return render(request, 'blog/post_form.html', {
|
||||
'form': form,
|
||||
'post': post
|
||||
})
|
||||
|
||||
# Class-Based Views
|
||||
class PostListView(ListView):
|
||||
"""List view for posts with pagination."""
|
||||
model = Post
|
||||
template_name = 'blog/post_list.html'
|
||||
context_object_name = 'posts'
|
||||
paginate_by = 10
|
||||
|
||||
def get_queryset(self):
|
||||
return Post.objects.filter(status='published').select_related('author', 'category')
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context['categories'] = Category.objects.all()
|
||||
return context
|
||||
|
||||
class PostDetailView(DetailView):
|
||||
"""Detail view for individual posts."""
|
||||
model = Post
|
||||
template_name = 'blog/post_detail.html'
|
||||
context_object_name = 'post'
|
||||
|
||||
def get_queryset(self):
|
||||
return Post.objects.filter(status='published')
|
||||
|
||||
class PostCreateView(LoginRequiredMixin, CreateView):
|
||||
"""Create view for new posts."""
|
||||
model = Post
|
||||
form_class = PostForm
|
||||
template_name = 'blog/post_form.html'
|
||||
|
||||
def form_valid(self, form):
|
||||
form.instance.author = self.request.user
|
||||
return super().form_valid(form)
|
||||
|
||||
class PostUpdateView(LoginRequiredMixin, UpdateView):
|
||||
"""Update view for existing posts."""
|
||||
model = Post
|
||||
form_class = PostForm
|
||||
template_name = 'blog/post_form.html'
|
||||
|
||||
def get_queryset(self):
|
||||
return Post.objects.filter(author=self.request.user)
|
||||
|
||||
class PostDeleteView(LoginRequiredMixin, DeleteView):
|
||||
"""Delete view for posts."""
|
||||
model = Post
|
||||
template_name = 'blog/post_confirm_delete.html'
|
||||
success_url = reverse_lazy('post_list')
|
||||
|
||||
def get_queryset(self):
|
||||
return Post.objects.filter(author=self.request.user)
|
||||
|
||||
# API Views
|
||||
def api_post_list(request):
|
||||
"""API endpoint for post list."""
|
||||
posts = Post.objects.filter(status='published').values(
|
||||
'id', 'title', 'slug', 'created_at', 'author__username'
|
||||
)
|
||||
return JsonResponse(list(posts), safe=False)
|
||||
```
|
||||
|
||||
## View Types Supported
|
||||
|
||||
- **Function-Based Views (FBV)** - Simple, explicit control
|
||||
- **Class-Based Views (CBV)** - Reusable, inheritance-based
|
||||
- **Generic Views** - ListView, DetailView, CreateView, etc.
|
||||
- **API Views** - JSON responses for AJAX/API calls
|
||||
|
||||
## Features Included
|
||||
|
||||
- **Authentication checks** with decorators/mixins
|
||||
- **Permission handling** for user authorization
|
||||
- **Form processing** with validation
|
||||
- **Error handling** and user feedback
|
||||
- **SEO-friendly URLs** with slugs
|
||||
- **Database optimization** with select_related/prefetch_related
|
||||
- **Pagination** for large datasets
|
||||
- **Search and filtering** functionality
|
||||
|
||||
## Security Best Practices
|
||||
|
||||
- CSRF protection (automatic with forms)
|
||||
- User authentication and authorization
|
||||
- SQL injection prevention (ORM)
|
||||
- XSS protection with template escaping
|
||||
- Proper error handling
|
||||
|
||||
## URL Configuration
|
||||
|
||||
```python
|
||||
# urls.py
|
||||
from django.urls import path
|
||||
from . import views
|
||||
|
||||
app_name = 'blog'
|
||||
|
||||
urlpatterns = [
|
||||
# Function-based views
|
||||
path('', views.post_list, name='post_list'),
|
||||
path('post/<slug:slug>/', views.post_detail, name='post_detail'),
|
||||
path('create/', views.post_create, name='post_create'),
|
||||
path('edit/<slug:slug>/', views.post_edit, name='post_edit'),
|
||||
|
||||
# Class-based views
|
||||
path('posts/', views.PostListView.as_view(), name='post_list_cbv'),
|
||||
path('posts/<slug:slug>/', views.PostDetailView.as_view(), name='post_detail_cbv'),
|
||||
path('posts/create/', views.PostCreateView.as_view(), name='post_create_cbv'),
|
||||
path('posts/<slug:slug>/edit/', views.PostUpdateView.as_view(), name='post_edit_cbv'),
|
||||
path('posts/<slug:slug>/delete/', views.PostDeleteView.as_view(), name='post_delete_cbv'),
|
||||
|
||||
# API endpoints
|
||||
path('api/posts/', views.api_post_list, name='api_post_list'),
|
||||
]
|
||||
```
|
||||
@@ -0,0 +1,313 @@
|
||||
# Django Project Configuration
|
||||
|
||||
This file provides specific guidance for Django web application development using Claude Code.
|
||||
|
||||
## Project Overview
|
||||
|
||||
This is a Django web application project optimized for scalable web development with the Django framework. The project follows Django best practices and conventions.
|
||||
|
||||
## Django-Specific Development Commands
|
||||
|
||||
### Project Management
|
||||
- `django-admin startproject myproject` - Create new Django project
|
||||
- `python manage.py startapp myapp` - Create new Django app
|
||||
- `python manage.py runserver` - Start development server
|
||||
- `python manage.py runserver 0.0.0.0:8000` - Start server accessible from network
|
||||
|
||||
### Database Management
|
||||
- `python manage.py makemigrations` - Create database migrations
|
||||
- `python manage.py migrate` - Apply database migrations
|
||||
- `python manage.py showmigrations` - Show migration status
|
||||
- `python manage.py sqlmigrate app_name migration_name` - Show SQL for migration
|
||||
- `python manage.py dbshell` - Open database shell
|
||||
|
||||
### User Management
|
||||
- `python manage.py createsuperuser` - Create admin superuser
|
||||
- `python manage.py changepassword username` - Change user password
|
||||
- `python manage.py shell` - Open Django shell
|
||||
|
||||
### Static Files & Media
|
||||
- `python manage.py collectstatic` - Collect static files for production
|
||||
- `python manage.py findstatic filename` - Find static file location
|
||||
|
||||
### Testing & Quality
|
||||
- `python manage.py test` - Run Django tests
|
||||
- `python manage.py test app_name` - Run tests for specific app
|
||||
- `python manage.py test --keepdb` - Run tests keeping test database
|
||||
- `coverage run --source='.' manage.py test` - Run tests with coverage
|
||||
|
||||
### Development Tools
|
||||
- `python manage.py check` - Check for Django issues
|
||||
- `python manage.py validate` - Validate models
|
||||
- `python manage.py inspectdb` - Generate models from existing database
|
||||
- `python manage.py dumpdata app_name` - Export data
|
||||
- `python manage.py loaddata fixture.json` - Import data
|
||||
|
||||
## Django Project Structure
|
||||
|
||||
```
|
||||
myproject/
|
||||
├── manage.py # Django management script
|
||||
├── myproject/ # Project configuration
|
||||
│ ├── __init__.py
|
||||
│ ├── settings/ # Settings modules
|
||||
│ │ ├── __init__.py
|
||||
│ │ ├── base.py # Base settings
|
||||
│ │ ├── development.py # Development settings
|
||||
│ │ ├── production.py # Production settings
|
||||
│ │ └── testing.py # Testing settings
|
||||
│ ├── urls.py # URL configuration
|
||||
│ ├── wsgi.py # WSGI configuration
|
||||
│ └── asgi.py # ASGI configuration
|
||||
├── apps/ # Django applications
|
||||
│ ├── users/ # User management app
|
||||
│ ├── blog/ # Blog app example
|
||||
│ └── api/ # API app
|
||||
├── static/ # Static files
|
||||
├── media/ # User uploaded files
|
||||
├── templates/ # Django templates
|
||||
├── requirements/ # Requirements files
|
||||
│ ├── base.txt
|
||||
│ ├── development.txt
|
||||
│ └── production.txt
|
||||
└── tests/ # Test files
|
||||
```
|
||||
|
||||
## Django Settings Configuration
|
||||
|
||||
### Base Settings (settings/base.py)
|
||||
```python
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
BASE_DIR = Path(__file__).resolve().parent.parent.parent
|
||||
|
||||
# Security
|
||||
SECRET_KEY = os.environ.get('SECRET_KEY')
|
||||
DEBUG = False
|
||||
ALLOWED_HOSTS = []
|
||||
|
||||
# Application definition
|
||||
DJANGO_APPS = [
|
||||
'django.contrib.admin',
|
||||
'django.contrib.auth',
|
||||
'django.contrib.contenttypes',
|
||||
'django.contrib.sessions',
|
||||
'django.contrib.messages',
|
||||
'django.contrib.staticfiles',
|
||||
]
|
||||
|
||||
THIRD_PARTY_APPS = [
|
||||
'rest_framework',
|
||||
'django_extensions',
|
||||
]
|
||||
|
||||
LOCAL_APPS = [
|
||||
'apps.users',
|
||||
'apps.blog',
|
||||
]
|
||||
|
||||
INSTALLED_APPS = DJANGO_APPS + THIRD_PARTY_APPS + LOCAL_APPS
|
||||
|
||||
# Database
|
||||
DATABASES = {
|
||||
'default': {
|
||||
'ENGINE': 'django.db.backends.postgresql',
|
||||
'NAME': os.environ.get('DB_NAME'),
|
||||
'USER': os.environ.get('DB_USER'),
|
||||
'PASSWORD': os.environ.get('DB_PASSWORD'),
|
||||
'HOST': os.environ.get('DB_HOST', 'localhost'),
|
||||
'PORT': os.environ.get('DB_PORT', '5432'),
|
||||
}
|
||||
}
|
||||
|
||||
# Internationalization
|
||||
LANGUAGE_CODE = 'en-us'
|
||||
TIME_ZONE = 'UTC'
|
||||
USE_I18N = True
|
||||
USE_TZ = True
|
||||
|
||||
# Static files
|
||||
STATIC_URL = '/static/'
|
||||
STATIC_ROOT = BASE_DIR / 'staticfiles'
|
||||
STATICFILES_DIRS = [BASE_DIR / 'static']
|
||||
|
||||
# Media files
|
||||
MEDIA_URL = '/media/'
|
||||
MEDIA_ROOT = BASE_DIR / 'media'
|
||||
```
|
||||
|
||||
## Django Best Practices
|
||||
|
||||
### Models
|
||||
- Use descriptive model names (singular)
|
||||
- Add `__str__` methods for better admin interface
|
||||
- Use `related_name` for foreign keys
|
||||
- Implement `get_absolute_url` method
|
||||
- Add proper Meta class with ordering
|
||||
|
||||
### Views
|
||||
- Use class-based views for complex logic
|
||||
- Implement proper error handling
|
||||
- Add pagination for list views
|
||||
- Use `select_related` and `prefetch_related` for optimization
|
||||
- Implement proper permission checks
|
||||
|
||||
### URLs
|
||||
- Use app namespaces
|
||||
- Use descriptive URL names
|
||||
- Group related URLs in separate files
|
||||
- Use slug fields for SEO-friendly URLs
|
||||
|
||||
### Templates
|
||||
- Extend base templates
|
||||
- Use template inheritance effectively
|
||||
- Create reusable template tags
|
||||
- Implement proper CSRF protection
|
||||
- Use Django's built-in template filters
|
||||
|
||||
### Forms
|
||||
- Use Django forms for validation
|
||||
- Implement custom form validation
|
||||
- Use ModelForms when appropriate
|
||||
- Add proper error handling
|
||||
- Implement CSRF protection
|
||||
|
||||
## Security Considerations
|
||||
|
||||
### Django Security Settings
|
||||
```python
|
||||
# Security settings for production
|
||||
SECURE_BROWSER_XSS_FILTER = True
|
||||
SECURE_CONTENT_TYPE_NOSNIFF = True
|
||||
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
|
||||
SECURE_HSTS_PRELOAD = True
|
||||
SECURE_HSTS_SECONDS = 31536000
|
||||
SECURE_REDIRECT_EXEMPT = []
|
||||
SECURE_SSL_REDIRECT = True
|
||||
SESSION_COOKIE_SECURE = True
|
||||
CSRF_COOKIE_SECURE = True
|
||||
```
|
||||
|
||||
### User Authentication
|
||||
- Use Django's built-in authentication
|
||||
- Implement proper password policies
|
||||
- Add two-factor authentication if needed
|
||||
- Use Django's permission system
|
||||
- Implement proper session management
|
||||
|
||||
## Testing Strategy
|
||||
|
||||
### Test Organization
|
||||
```python
|
||||
# tests/test_models.py
|
||||
from django.test import TestCase
|
||||
from django.contrib.auth import get_user_model
|
||||
from apps.blog.models import Post
|
||||
|
||||
User = get_user_model()
|
||||
|
||||
class PostModelTest(TestCase):
|
||||
def setUp(self):
|
||||
self.user = User.objects.create_user(
|
||||
username='testuser',
|
||||
email='test@example.com',
|
||||
password='testpass123'
|
||||
)
|
||||
|
||||
def test_post_creation(self):
|
||||
post = Post.objects.create(
|
||||
title='Test Post',
|
||||
content='Test content',
|
||||
author=self.user
|
||||
)
|
||||
self.assertEqual(post.title, 'Test Post')
|
||||
self.assertEqual(str(post), 'Test Post')
|
||||
```
|
||||
|
||||
### Test Types
|
||||
- **Unit tests** for models and utilities
|
||||
- **Integration tests** for views and forms
|
||||
- **Functional tests** for user workflows
|
||||
- **API tests** for REST endpoints
|
||||
|
||||
## Deployment Considerations
|
||||
|
||||
### Production Settings
|
||||
- Use environment variables for sensitive data
|
||||
- Configure proper logging
|
||||
- Set up static file serving
|
||||
- Configure database connection pooling
|
||||
- Implement proper caching strategy
|
||||
|
||||
### Docker Configuration
|
||||
```dockerfile
|
||||
FROM python:3.11-slim
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY requirements.txt .
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
|
||||
COPY . .
|
||||
|
||||
EXPOSE 8000
|
||||
|
||||
CMD ["gunicorn", "--bind", "0.0.0.0:8000", "myproject.wsgi:application"]
|
||||
```
|
||||
|
||||
## Performance Optimization
|
||||
|
||||
### Database Optimization
|
||||
- Use `select_related()` for foreign keys
|
||||
- Use `prefetch_related()` for many-to-many
|
||||
- Add database indexes for frequent queries
|
||||
- Implement database connection pooling
|
||||
- Use database query optimization tools
|
||||
|
||||
### Caching Strategy
|
||||
- Implement Redis/Memcached for session storage
|
||||
- Use template fragment caching
|
||||
- Implement view-level caching
|
||||
- Add database query caching
|
||||
- Use CDN for static files
|
||||
|
||||
## Common Django Patterns
|
||||
|
||||
### Custom User Model
|
||||
```python
|
||||
from django.contrib.auth.models import AbstractUser
|
||||
from django.db import models
|
||||
|
||||
class User(AbstractUser):
|
||||
email = models.EmailField(unique=True)
|
||||
first_name = models.CharField(max_length=30)
|
||||
last_name = models.CharField(max_length=30)
|
||||
|
||||
USERNAME_FIELD = 'email'
|
||||
REQUIRED_FIELDS = ['username', 'first_name', 'last_name']
|
||||
```
|
||||
|
||||
### Custom Managers
|
||||
```python
|
||||
class PublishedManager(models.Manager):
|
||||
def get_queryset(self):
|
||||
return super().get_queryset().filter(status='published')
|
||||
|
||||
class Post(models.Model):
|
||||
title = models.CharField(max_length=200)
|
||||
status = models.CharField(max_length=20, default='draft')
|
||||
|
||||
objects = models.Manager() # Default manager
|
||||
published = PublishedManager() # Custom manager
|
||||
```
|
||||
|
||||
## Django Extensions & Tools
|
||||
|
||||
### Useful Third-Party Packages
|
||||
- **Django REST Framework** - API development
|
||||
- **Celery** - Asynchronous task processing
|
||||
- **Django Debug Toolbar** - Development debugging
|
||||
- **Django Extensions** - Additional management commands
|
||||
- **Pillow** - Image processing
|
||||
- **psycopg2-binary** - PostgreSQL adapter
|
||||
@@ -0,0 +1,513 @@
|
||||
# FastAPI Endpoints Generator
|
||||
|
||||
Create comprehensive FastAPI endpoints with proper structure, validation, and documentation.
|
||||
|
||||
## Purpose
|
||||
|
||||
This command helps you quickly create FastAPI endpoints with Pydantic models, dependency injection, and automatic API documentation.
|
||||
|
||||
## Usage
|
||||
|
||||
```
|
||||
/api-endpoints
|
||||
```
|
||||
|
||||
## What this command does
|
||||
|
||||
1. **Creates API endpoints** with proper HTTP methods
|
||||
2. **Adds Pydantic models** for request/response validation
|
||||
3. **Implements dependency injection** for database and auth
|
||||
4. **Includes error handling** and status codes
|
||||
5. **Generates automatic documentation** with OpenAPI
|
||||
|
||||
## Example Output
|
||||
|
||||
```python
|
||||
# main.py
|
||||
from fastapi import FastAPI, Depends, HTTPException, status
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
|
||||
from sqlalchemy.orm import Session
|
||||
import uvicorn
|
||||
|
||||
from app.database import get_db, engine
|
||||
from app.models import models
|
||||
from app.routers import auth, users, posts, comments
|
||||
from app.core.config import settings
|
||||
|
||||
# Create database tables
|
||||
models.Base.metadata.create_all(bind=engine)
|
||||
|
||||
# Initialize FastAPI app
|
||||
app = FastAPI(
|
||||
title="Blog API",
|
||||
description="A comprehensive blog API built with FastAPI",
|
||||
version="1.0.0",
|
||||
docs_url="/docs",
|
||||
redoc_url="/redoc"
|
||||
)
|
||||
|
||||
# Add CORS middleware
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=settings.ALLOWED_HOSTS,
|
||||
allow_credentials=True,
|
||||
allow_methods=["*"],
|
||||
allow_headers=["*"],
|
||||
)
|
||||
|
||||
# Include routers
|
||||
app.include_router(auth.router, prefix="/auth", tags=["Authentication"])
|
||||
app.include_router(users.router, prefix="/users", tags=["Users"])
|
||||
app.include_router(posts.router, prefix="/posts", tags=["Posts"])
|
||||
app.include_router(comments.router, prefix="/comments", tags=["Comments"])
|
||||
|
||||
@app.get("/", tags=["Root"])
|
||||
async def root():
|
||||
"""API root endpoint."""
|
||||
return {
|
||||
"message": "Welcome to Blog API",
|
||||
"version": "1.0.0",
|
||||
"docs": "/docs",
|
||||
"redoc": "/redoc"
|
||||
}
|
||||
|
||||
@app.get("/health", tags=["Health"])
|
||||
async def health_check():
|
||||
"""Health check endpoint."""
|
||||
return {"status": "healthy", "timestamp": "2024-01-01T00:00:00Z"}
|
||||
|
||||
if __name__ == "__main__":
|
||||
uvicorn.run(
|
||||
"main:app",
|
||||
host="0.0.0.0",
|
||||
port=8000,
|
||||
reload=True
|
||||
)
|
||||
```
|
||||
|
||||
```python
|
||||
# app/routers/posts.py
|
||||
from typing import List, Optional
|
||||
from fastapi import APIRouter, Depends, HTTPException, status, Query
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.database import get_db
|
||||
from app.schemas import post_schemas
|
||||
from app.services import post_service
|
||||
from app.core.dependencies import get_current_user, get_current_active_user
|
||||
from app.models.user import User
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
@router.get("/", response_model=List[post_schemas.PostResponse])
|
||||
async def get_posts(
|
||||
skip: int = Query(0, ge=0, description="Number of posts to skip"),
|
||||
limit: int = Query(10, ge=1, le=100, description="Number of posts to return"),
|
||||
search: Optional[str] = Query(None, description="Search in title and content"),
|
||||
category: Optional[str] = Query(None, description="Filter by category"),
|
||||
published: Optional[bool] = Query(True, description="Filter by published status"),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""
|
||||
Get all posts with pagination and filtering.
|
||||
|
||||
- **skip**: Number of posts to skip (for pagination)
|
||||
- **limit**: Maximum number of posts to return (1-100)
|
||||
- **search**: Search term for title and content
|
||||
- **category**: Filter posts by category
|
||||
- **published**: Filter by published status
|
||||
"""
|
||||
posts = post_service.get_posts(
|
||||
db=db,
|
||||
skip=skip,
|
||||
limit=limit,
|
||||
search=search,
|
||||
category=category,
|
||||
published=published
|
||||
)
|
||||
return posts
|
||||
|
||||
@router.get("/{post_id}", response_model=post_schemas.PostResponse)
|
||||
async def get_post(
|
||||
post_id: int,
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""
|
||||
Get a specific post by ID.
|
||||
|
||||
- **post_id**: Unique identifier for the post
|
||||
"""
|
||||
post = post_service.get_post(db=db, post_id=post_id)
|
||||
if not post:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="Post not found"
|
||||
)
|
||||
return post
|
||||
|
||||
@router.post("/", response_model=post_schemas.PostResponse, status_code=status.HTTP_201_CREATED)
|
||||
async def create_post(
|
||||
post: post_schemas.PostCreate,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_active_user)
|
||||
):
|
||||
"""
|
||||
Create a new post.
|
||||
|
||||
- **title**: Post title (required)
|
||||
- **content**: Post content (required)
|
||||
- **category**: Post category (optional)
|
||||
- **published**: Publication status (default: false)
|
||||
"""
|
||||
return post_service.create_post(
|
||||
db=db,
|
||||
post=post,
|
||||
user_id=current_user.id
|
||||
)
|
||||
|
||||
@router.put("/{post_id}", response_model=post_schemas.PostResponse)
|
||||
async def update_post(
|
||||
post_id: int,
|
||||
post_update: post_schemas.PostUpdate,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_active_user)
|
||||
):
|
||||
"""
|
||||
Update an existing post.
|
||||
|
||||
- **post_id**: Unique identifier for the post
|
||||
- **title**: Updated post title (optional)
|
||||
- **content**: Updated post content (optional)
|
||||
- **category**: Updated post category (optional)
|
||||
- **published**: Updated publication status (optional)
|
||||
"""
|
||||
post = post_service.get_post(db=db, post_id=post_id)
|
||||
if not post:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="Post not found"
|
||||
)
|
||||
|
||||
if post.author_id != current_user.id:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="Not authorized to update this post"
|
||||
)
|
||||
|
||||
return post_service.update_post(
|
||||
db=db,
|
||||
post_id=post_id,
|
||||
post_update=post_update
|
||||
)
|
||||
|
||||
@router.delete("/{post_id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def delete_post(
|
||||
post_id: int,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_active_user)
|
||||
):
|
||||
"""
|
||||
Delete a post.
|
||||
|
||||
- **post_id**: Unique identifier for the post to delete
|
||||
"""
|
||||
post = post_service.get_post(db=db, post_id=post_id)
|
||||
if not post:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="Post not found"
|
||||
)
|
||||
|
||||
if post.author_id != current_user.id:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="Not authorized to delete this post"
|
||||
)
|
||||
|
||||
post_service.delete_post(db=db, post_id=post_id)
|
||||
|
||||
@router.post("/{post_id}/like", response_model=post_schemas.PostResponse)
|
||||
async def like_post(
|
||||
post_id: int,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_active_user)
|
||||
):
|
||||
"""
|
||||
Like/unlike a post.
|
||||
|
||||
- **post_id**: Unique identifier for the post to like
|
||||
"""
|
||||
post = post_service.get_post(db=db, post_id=post_id)
|
||||
if not post:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="Post not found"
|
||||
)
|
||||
|
||||
return post_service.toggle_like(
|
||||
db=db,
|
||||
post_id=post_id,
|
||||
user_id=current_user.id
|
||||
)
|
||||
|
||||
@router.get("/{post_id}/comments", response_model=List[post_schemas.CommentResponse])
|
||||
async def get_post_comments(
|
||||
post_id: int,
|
||||
skip: int = Query(0, ge=0),
|
||||
limit: int = Query(10, ge=1, le=100),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""
|
||||
Get all comments for a specific post.
|
||||
|
||||
- **post_id**: Unique identifier for the post
|
||||
- **skip**: Number of comments to skip
|
||||
- **limit**: Maximum number of comments to return
|
||||
"""
|
||||
post = post_service.get_post(db=db, post_id=post_id)
|
||||
if not post:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="Post not found"
|
||||
)
|
||||
|
||||
return post_service.get_post_comments(
|
||||
db=db,
|
||||
post_id=post_id,
|
||||
skip=skip,
|
||||
limit=limit
|
||||
)
|
||||
```
|
||||
|
||||
```python
|
||||
# app/schemas/post_schemas.py
|
||||
from typing import Optional, List
|
||||
from datetime import datetime
|
||||
from pydantic import BaseModel, Field, validator
|
||||
|
||||
class PostBase(BaseModel):
|
||||
"""Base post schema."""
|
||||
title: str = Field(..., min_length=1, max_length=200, description="Post title")
|
||||
content: str = Field(..., min_length=1, description="Post content")
|
||||
category: Optional[str] = Field(None, max_length=50, description="Post category")
|
||||
published: bool = Field(False, description="Publication status")
|
||||
|
||||
class PostCreate(PostBase):
|
||||
"""Schema for creating a post."""
|
||||
|
||||
@validator('title')
|
||||
def validate_title(cls, v):
|
||||
if not v.strip():
|
||||
raise ValueError('Title cannot be empty')
|
||||
return v.strip()
|
||||
|
||||
@validator('content')
|
||||
def validate_content(cls, v):
|
||||
if len(v.strip()) < 10:
|
||||
raise ValueError('Content must be at least 10 characters long')
|
||||
return v.strip()
|
||||
|
||||
class PostUpdate(BaseModel):
|
||||
"""Schema for updating a post."""
|
||||
title: Optional[str] = Field(None, min_length=1, max_length=200)
|
||||
content: Optional[str] = Field(None, min_length=1)
|
||||
category: Optional[str] = Field(None, max_length=50)
|
||||
published: Optional[bool] = None
|
||||
|
||||
@validator('title')
|
||||
def validate_title(cls, v):
|
||||
if v is not None and not v.strip():
|
||||
raise ValueError('Title cannot be empty')
|
||||
return v.strip() if v else v
|
||||
|
||||
@validator('content')
|
||||
def validate_content(cls, v):
|
||||
if v is not None and len(v.strip()) < 10:
|
||||
raise ValueError('Content must be at least 10 characters long')
|
||||
return v.strip() if v else v
|
||||
|
||||
class PostResponse(PostBase):
|
||||
"""Schema for post responses."""
|
||||
id: int
|
||||
author_id: int
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
like_count: int = 0
|
||||
comment_count: int = 0
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
class CommentBase(BaseModel):
|
||||
"""Base comment schema."""
|
||||
content: str = Field(..., min_length=1, max_length=1000, description="Comment content")
|
||||
|
||||
class CommentCreate(CommentBase):
|
||||
"""Schema for creating a comment."""
|
||||
post_id: int = Field(..., description="ID of the post to comment on")
|
||||
|
||||
class CommentResponse(CommentBase):
|
||||
"""Schema for comment responses."""
|
||||
id: int
|
||||
post_id: int
|
||||
author_id: int
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
```
|
||||
|
||||
```python
|
||||
# app/services/post_service.py
|
||||
from typing import List, Optional
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy import or_
|
||||
|
||||
from app.models.post import Post, Comment, PostLike
|
||||
from app.schemas.post_schemas import PostCreate, PostUpdate
|
||||
|
||||
def get_posts(
|
||||
db: Session,
|
||||
skip: int = 0,
|
||||
limit: int = 10,
|
||||
search: Optional[str] = None,
|
||||
category: Optional[str] = None,
|
||||
published: Optional[bool] = True
|
||||
) -> List[Post]:
|
||||
"""Get posts with filtering and pagination."""
|
||||
query = db.query(Post)
|
||||
|
||||
if published is not None:
|
||||
query = query.filter(Post.published == published)
|
||||
|
||||
if category:
|
||||
query = query.filter(Post.category == category)
|
||||
|
||||
if search:
|
||||
query = query.filter(
|
||||
or_(
|
||||
Post.title.contains(search),
|
||||
Post.content.contains(search)
|
||||
)
|
||||
)
|
||||
|
||||
return query.offset(skip).limit(limit).all()
|
||||
|
||||
def get_post(db: Session, post_id: int) -> Optional[Post]:
|
||||
"""Get a single post by ID."""
|
||||
return db.query(Post).filter(Post.id == post_id).first()
|
||||
|
||||
def create_post(db: Session, post: PostCreate, user_id: int) -> Post:
|
||||
"""Create a new post."""
|
||||
db_post = Post(**post.dict(), author_id=user_id)
|
||||
db.add(db_post)
|
||||
db.commit()
|
||||
db.refresh(db_post)
|
||||
return db_post
|
||||
|
||||
def update_post(
|
||||
db: Session,
|
||||
post_id: int,
|
||||
post_update: PostUpdate
|
||||
) -> Optional[Post]:
|
||||
"""Update an existing post."""
|
||||
db_post = db.query(Post).filter(Post.id == post_id).first()
|
||||
if not db_post:
|
||||
return None
|
||||
|
||||
update_data = post_update.dict(exclude_unset=True)
|
||||
for field, value in update_data.items():
|
||||
setattr(db_post, field, value)
|
||||
|
||||
db.commit()
|
||||
db.refresh(db_post)
|
||||
return db_post
|
||||
|
||||
def delete_post(db: Session, post_id: int) -> bool:
|
||||
"""Delete a post."""
|
||||
db_post = db.query(Post).filter(Post.id == post_id).first()
|
||||
if not db_post:
|
||||
return False
|
||||
|
||||
db.delete(db_post)
|
||||
db.commit()
|
||||
return True
|
||||
|
||||
def toggle_like(db: Session, post_id: int, user_id: int) -> Post:
|
||||
"""Toggle like status for a post."""
|
||||
existing_like = db.query(PostLike).filter(
|
||||
PostLike.post_id == post_id,
|
||||
PostLike.user_id == user_id
|
||||
).first()
|
||||
|
||||
if existing_like:
|
||||
db.delete(existing_like)
|
||||
else:
|
||||
new_like = PostLike(post_id=post_id, user_id=user_id)
|
||||
db.add(new_like)
|
||||
|
||||
db.commit()
|
||||
return get_post(db, post_id)
|
||||
```
|
||||
|
||||
## Features Included
|
||||
|
||||
### API Documentation
|
||||
- **Automatic OpenAPI** schema generation
|
||||
- **Interactive docs** at `/docs`
|
||||
- **ReDoc documentation** at `/redoc`
|
||||
- **Request/Response examples** in schemas
|
||||
|
||||
### Validation & Serialization
|
||||
- **Pydantic models** for data validation
|
||||
- **Custom validators** for business rules
|
||||
- **Type hints** for better IDE support
|
||||
- **Automatic data conversion** and validation
|
||||
|
||||
### Error Handling
|
||||
- **HTTP status codes** for different scenarios
|
||||
- **Detailed error messages** with context
|
||||
- **Input validation errors** with field-specific messages
|
||||
- **Custom exception handlers** for consistent responses
|
||||
|
||||
### Security
|
||||
- **JWT authentication** with dependencies
|
||||
- **Role-based access control** for endpoints
|
||||
- **CORS middleware** for cross-origin requests
|
||||
- **Input sanitization** through Pydantic
|
||||
|
||||
### Performance
|
||||
- **Database query optimization** with SQLAlchemy
|
||||
- **Pagination support** for large datasets
|
||||
- **Async/await support** for concurrent requests
|
||||
- **Connection pooling** for database efficiency
|
||||
|
||||
## Testing Example
|
||||
|
||||
```python
|
||||
# tests/test_posts.py
|
||||
import pytest
|
||||
from fastapi.testclient import TestClient
|
||||
from app.main import app
|
||||
|
||||
client = TestClient(app)
|
||||
|
||||
def test_get_posts():
|
||||
"""Test getting posts."""
|
||||
response = client.get("/posts/")
|
||||
assert response.status_code == 200
|
||||
assert isinstance(response.json(), list)
|
||||
|
||||
def test_create_post():
|
||||
"""Test creating a new post."""
|
||||
post_data = {
|
||||
"title": "Test Post",
|
||||
"content": "This is a test post content.",
|
||||
"published": True
|
||||
}
|
||||
response = client.post("/posts/", json=post_data)
|
||||
assert response.status_code == 201
|
||||
assert response.json()["title"] == "Test Post"
|
||||
```
|
||||
@@ -0,0 +1,775 @@
|
||||
# FastAPI Authentication & Authorization
|
||||
|
||||
Complete authentication system with JWT tokens, OAuth2, and role-based access control.
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
# Install auth dependencies
|
||||
pip install python-jose[cryptography] passlib[bcrypt] python-multipart
|
||||
|
||||
# Generate secret key
|
||||
openssl rand -hex 32
|
||||
```
|
||||
|
||||
## JWT Configuration
|
||||
|
||||
```python
|
||||
# app/core/security.py
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Optional, Union, Any
|
||||
from jose import JWTError, jwt
|
||||
from passlib.context import CryptContext
|
||||
from app.core.config import settings
|
||||
|
||||
# Password hashing
|
||||
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
|
||||
|
||||
# JWT settings
|
||||
SECRET_KEY = settings.SECRET_KEY
|
||||
ALGORITHM = "HS256"
|
||||
ACCESS_TOKEN_EXPIRE_MINUTES = 30
|
||||
REFRESH_TOKEN_EXPIRE_DAYS = 7
|
||||
|
||||
def create_access_token(
|
||||
subject: Union[str, Any],
|
||||
expires_delta: Optional[timedelta] = None
|
||||
) -> str:
|
||||
"""Create JWT access token."""
|
||||
if expires_delta:
|
||||
expire = datetime.utcnow() + expires_delta
|
||||
else:
|
||||
expire = datetime.utcnow() + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
|
||||
|
||||
to_encode = {"exp": expire, "sub": str(subject), "type": "access"}
|
||||
encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
|
||||
return encoded_jwt
|
||||
|
||||
def create_refresh_token(subject: Union[str, Any]) -> str:
|
||||
"""Create JWT refresh token."""
|
||||
expire = datetime.utcnow() + timedelta(days=REFRESH_TOKEN_EXPIRE_DAYS)
|
||||
to_encode = {"exp": expire, "sub": str(subject), "type": "refresh"}
|
||||
encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
|
||||
return encoded_jwt
|
||||
|
||||
def verify_password(plain_password: str, hashed_password: str) -> bool:
|
||||
"""Verify password against hash."""
|
||||
return pwd_context.verify(plain_password, hashed_password)
|
||||
|
||||
def get_password_hash(password: str) -> str:
|
||||
"""Generate password hash."""
|
||||
return pwd_context.hash(password)
|
||||
|
||||
def decode_token(token: str) -> Optional[dict]:
|
||||
"""Decode and verify JWT token."""
|
||||
try:
|
||||
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
|
||||
return payload
|
||||
except JWTError:
|
||||
return None
|
||||
```
|
||||
|
||||
## Authentication Dependencies
|
||||
|
||||
```python
|
||||
# app/api/dependencies/auth.py
|
||||
from typing import Optional
|
||||
from fastapi import Depends, HTTPException, status
|
||||
from fastapi.security import OAuth2PasswordBearer, HTTPBearer, HTTPAuthorizationCredentials
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from app.core.security import decode_token
|
||||
from app.db.database import get_db
|
||||
from app.models.user import User
|
||||
from app.repositories.user import UserRepository
|
||||
|
||||
# OAuth2 scheme
|
||||
oauth2_scheme = OAuth2PasswordBearer(
|
||||
tokenUrl="/api/v1/auth/login",
|
||||
scheme_name="JWT"
|
||||
)
|
||||
|
||||
# Bearer token scheme
|
||||
security = HTTPBearer()
|
||||
|
||||
async def get_current_user(
|
||||
token: str = Depends(oauth2_scheme),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
) -> User:
|
||||
"""Get current authenticated user."""
|
||||
credentials_exception = HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Could not validate credentials",
|
||||
headers={"WWW-Authenticate": "Bearer"},
|
||||
)
|
||||
|
||||
payload = decode_token(token)
|
||||
if payload is None:
|
||||
raise credentials_exception
|
||||
|
||||
user_id: str = payload.get("sub")
|
||||
token_type: str = payload.get("type")
|
||||
|
||||
if user_id is None or token_type != "access":
|
||||
raise credentials_exception
|
||||
|
||||
user_repo = UserRepository(User, db)
|
||||
user = await user_repo.get(int(user_id))
|
||||
|
||||
if user is None:
|
||||
raise credentials_exception
|
||||
|
||||
if not user.is_active:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="Inactive user"
|
||||
)
|
||||
|
||||
return user
|
||||
|
||||
async def get_current_active_user(
|
||||
current_user: User = Depends(get_current_user)
|
||||
) -> User:
|
||||
"""Get current active user."""
|
||||
if not current_user.is_active:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="Inactive user"
|
||||
)
|
||||
return current_user
|
||||
|
||||
async def get_current_superuser(
|
||||
current_user: User = Depends(get_current_user)
|
||||
) -> User:
|
||||
"""Get current superuser."""
|
||||
if not current_user.is_superuser:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="Not enough permissions"
|
||||
)
|
||||
return current_user
|
||||
|
||||
def require_permissions(*permissions: str):
|
||||
"""Decorator for permission-based access control."""
|
||||
async def permission_checker(
|
||||
current_user: User = Depends(get_current_active_user)
|
||||
) -> User:
|
||||
# Check if user has required permissions
|
||||
user_permissions = set(current_user.permissions or [])
|
||||
required_permissions = set(permissions)
|
||||
|
||||
if not required_permissions.issubset(user_permissions) and not current_user.is_superuser:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="Insufficient permissions"
|
||||
)
|
||||
|
||||
return current_user
|
||||
|
||||
return permission_checker
|
||||
|
||||
def require_roles(*roles: str):
|
||||
"""Decorator for role-based access control."""
|
||||
async def role_checker(
|
||||
current_user: User = Depends(get_current_active_user)
|
||||
) -> User:
|
||||
user_roles = set(role.name for role in current_user.roles or [])
|
||||
required_roles = set(roles)
|
||||
|
||||
if not required_roles.issubset(user_roles) and not current_user.is_superuser:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="Insufficient role permissions"
|
||||
)
|
||||
|
||||
return current_user
|
||||
|
||||
return role_checker
|
||||
```
|
||||
|
||||
## Authentication Schemas
|
||||
|
||||
```python
|
||||
# app/schemas/auth.py
|
||||
from pydantic import BaseModel, EmailStr
|
||||
from typing import Optional
|
||||
|
||||
class Token(BaseModel):
|
||||
"""Token response schema."""
|
||||
access_token: str
|
||||
refresh_token: str
|
||||
token_type: str = "bearer"
|
||||
expires_in: int
|
||||
|
||||
class TokenPayload(BaseModel):
|
||||
"""Token payload schema."""
|
||||
sub: Optional[int] = None
|
||||
exp: Optional[int] = None
|
||||
type: Optional[str] = None
|
||||
|
||||
class UserLogin(BaseModel):
|
||||
"""User login schema."""
|
||||
username: str
|
||||
password: str
|
||||
|
||||
class UserRegister(BaseModel):
|
||||
"""User registration schema."""
|
||||
username: str
|
||||
email: EmailStr
|
||||
password: str
|
||||
first_name: str
|
||||
last_name: str
|
||||
|
||||
class PasswordReset(BaseModel):
|
||||
"""Password reset schema."""
|
||||
email: EmailStr
|
||||
|
||||
class PasswordResetConfirm(BaseModel):
|
||||
"""Password reset confirmation schema."""
|
||||
token: str
|
||||
new_password: str
|
||||
|
||||
class ChangePassword(BaseModel):
|
||||
"""Change password schema."""
|
||||
current_password: str
|
||||
new_password: str
|
||||
|
||||
class RefreshToken(BaseModel):
|
||||
"""Refresh token schema."""
|
||||
refresh_token: str
|
||||
```
|
||||
|
||||
## Authentication Endpoints
|
||||
|
||||
```python
|
||||
# app/api/v1/auth.py
|
||||
from datetime import timedelta
|
||||
from typing import Any
|
||||
from fastapi import APIRouter, Depends, HTTPException, status, BackgroundTasks
|
||||
from fastapi.security import OAuth2PasswordRequestForm
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from app.db.database import get_db
|
||||
from app.models.user import User
|
||||
from app.repositories.user import UserRepository
|
||||
from app.schemas.auth import (
|
||||
Token, UserLogin, UserRegister, PasswordReset,
|
||||
PasswordResetConfirm, ChangePassword, RefreshToken
|
||||
)
|
||||
from app.schemas.user import UserCreate, UserResponse
|
||||
from app.core.security import (
|
||||
create_access_token, create_refresh_token, verify_password,
|
||||
decode_token, ACCESS_TOKEN_EXPIRE_MINUTES
|
||||
)
|
||||
from app.api.dependencies.auth import get_current_user, get_current_active_user
|
||||
from app.services.email import send_password_reset_email
|
||||
from app.services.auth import AuthService
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
@router.post("/register", response_model=UserResponse, status_code=status.HTTP_201_CREATED)
|
||||
async def register(
|
||||
user_data: UserRegister,
|
||||
db: AsyncSession = Depends(get_db)
|
||||
) -> Any:
|
||||
"""Register new user."""
|
||||
user_repo = UserRepository(User, db)
|
||||
auth_service = AuthService(user_repo)
|
||||
|
||||
# Check if user already exists
|
||||
if await user_repo.get_by_email(user_data.email):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="Email already registered"
|
||||
)
|
||||
|
||||
if await user_repo.get_by_username(user_data.username):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="Username already taken"
|
||||
)
|
||||
|
||||
# Create user
|
||||
user = await auth_service.create_user(user_data.dict())
|
||||
return user
|
||||
|
||||
@router.post("/login", response_model=Token)
|
||||
async def login(
|
||||
form_data: OAuth2PasswordRequestForm = Depends(),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
) -> Any:
|
||||
"""OAuth2 compatible token login."""
|
||||
user_repo = UserRepository(User, db)
|
||||
auth_service = AuthService(user_repo)
|
||||
|
||||
user = await auth_service.authenticate_user(
|
||||
form_data.username,
|
||||
form_data.password
|
||||
)
|
||||
|
||||
if not user:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Incorrect username or password",
|
||||
headers={"WWW-Authenticate": "Bearer"},
|
||||
)
|
||||
|
||||
if not user.is_active:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="Inactive user"
|
||||
)
|
||||
|
||||
# Create tokens
|
||||
access_token = create_access_token(subject=user.id)
|
||||
refresh_token = create_refresh_token(subject=user.id)
|
||||
|
||||
return {
|
||||
"access_token": access_token,
|
||||
"refresh_token": refresh_token,
|
||||
"token_type": "bearer",
|
||||
"expires_in": ACCESS_TOKEN_EXPIRE_MINUTES * 60
|
||||
}
|
||||
|
||||
@router.post("/refresh", response_model=Token)
|
||||
async def refresh_token(
|
||||
refresh_data: RefreshToken,
|
||||
db: AsyncSession = Depends(get_db)
|
||||
) -> Any:
|
||||
"""Refresh access token."""
|
||||
payload = decode_token(refresh_data.refresh_token)
|
||||
|
||||
if payload is None or payload.get("type") != "refresh":
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Invalid refresh token"
|
||||
)
|
||||
|
||||
user_id = payload.get("sub")
|
||||
if user_id is None:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Invalid refresh token"
|
||||
)
|
||||
|
||||
user_repo = UserRepository(User, db)
|
||||
user = await user_repo.get(int(user_id))
|
||||
|
||||
if user is None or not user.is_active:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Invalid refresh token"
|
||||
)
|
||||
|
||||
# Create new tokens
|
||||
access_token = create_access_token(subject=user.id)
|
||||
new_refresh_token = create_refresh_token(subject=user.id)
|
||||
|
||||
return {
|
||||
"access_token": access_token,
|
||||
"refresh_token": new_refresh_token,
|
||||
"token_type": "bearer",
|
||||
"expires_in": ACCESS_TOKEN_EXPIRE_MINUTES * 60
|
||||
}
|
||||
|
||||
@router.get("/me", response_model=UserResponse)
|
||||
async def read_users_me(
|
||||
current_user: User = Depends(get_current_active_user)
|
||||
) -> Any:
|
||||
"""Get current user."""
|
||||
return current_user
|
||||
|
||||
@router.post("/change-password", status_code=status.HTTP_200_OK)
|
||||
async def change_password(
|
||||
password_data: ChangePassword,
|
||||
current_user: User = Depends(get_current_active_user),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
) -> Any:
|
||||
"""Change user password."""
|
||||
if not verify_password(password_data.current_password, current_user.hashed_password):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="Incorrect current password"
|
||||
)
|
||||
|
||||
user_repo = UserRepository(User, db)
|
||||
auth_service = AuthService(user_repo)
|
||||
|
||||
await auth_service.change_password(current_user.id, password_data.new_password)
|
||||
|
||||
return {"message": "Password changed successfully"}
|
||||
|
||||
@router.post("/password-reset", status_code=status.HTTP_200_OK)
|
||||
async def password_reset(
|
||||
reset_data: PasswordReset,
|
||||
background_tasks: BackgroundTasks,
|
||||
db: AsyncSession = Depends(get_db)
|
||||
) -> Any:
|
||||
"""Request password reset."""
|
||||
user_repo = UserRepository(User, db)
|
||||
user = await user_repo.get_by_email(reset_data.email)
|
||||
|
||||
if user:
|
||||
# Generate reset token
|
||||
reset_token = create_access_token(
|
||||
subject=user.id,
|
||||
expires_delta=timedelta(hours=1) # 1 hour expiry
|
||||
)
|
||||
|
||||
# Send email with reset token
|
||||
background_tasks.add_task(
|
||||
send_password_reset_email,
|
||||
email=user.email,
|
||||
username=user.username,
|
||||
token=reset_token
|
||||
)
|
||||
|
||||
# Always return success to prevent email enumeration
|
||||
return {"message": "Password reset email sent if account exists"}
|
||||
|
||||
@router.post("/password-reset-confirm", status_code=status.HTTP_200_OK)
|
||||
async def password_reset_confirm(
|
||||
reset_data: PasswordResetConfirm,
|
||||
db: AsyncSession = Depends(get_db)
|
||||
) -> Any:
|
||||
"""Confirm password reset."""
|
||||
payload = decode_token(reset_data.token)
|
||||
|
||||
if payload is None:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="Invalid or expired reset token"
|
||||
)
|
||||
|
||||
user_id = payload.get("sub")
|
||||
if user_id is None:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="Invalid reset token"
|
||||
)
|
||||
|
||||
user_repo = UserRepository(User, db)
|
||||
auth_service = AuthService(user_repo)
|
||||
|
||||
user = await user_repo.get(int(user_id))
|
||||
if user is None:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="Invalid reset token"
|
||||
)
|
||||
|
||||
await auth_service.change_password(user.id, reset_data.new_password)
|
||||
|
||||
return {"message": "Password reset successful"}
|
||||
|
||||
@router.post("/logout", status_code=status.HTTP_200_OK)
|
||||
async def logout(
|
||||
current_user: User = Depends(get_current_user)
|
||||
) -> Any:
|
||||
"""Logout user (invalidate token on client side)."""
|
||||
# In a more sophisticated setup, you might want to blacklist the token
|
||||
return {"message": "Successfully logged out"}
|
||||
```
|
||||
|
||||
## Authentication Service
|
||||
|
||||
```python
|
||||
# app/services/auth.py
|
||||
from typing import Optional
|
||||
from app.models.user import User
|
||||
from app.repositories.user import UserRepository
|
||||
from app.core.security import verify_password, get_password_hash
|
||||
|
||||
class AuthService:
|
||||
"""Authentication service."""
|
||||
|
||||
def __init__(self, user_repository: UserRepository):
|
||||
self.user_repo = user_repository
|
||||
|
||||
async def authenticate_user(
|
||||
self,
|
||||
username_or_email: str,
|
||||
password: str
|
||||
) -> Optional[User]:
|
||||
"""Authenticate user by username/email and password."""
|
||||
# Try to get user by username first, then by email
|
||||
user = await self.user_repo.get_by_username(username_or_email)
|
||||
if not user:
|
||||
user = await self.user_repo.get_by_email(username_or_email)
|
||||
|
||||
if not user:
|
||||
return None
|
||||
|
||||
if not verify_password(password, user.hashed_password):
|
||||
return None
|
||||
|
||||
return user
|
||||
|
||||
async def create_user(self, user_data: dict) -> User:
|
||||
"""Create new user."""
|
||||
# Hash password
|
||||
password = user_data.pop('password')
|
||||
hashed_password = get_password_hash(password)
|
||||
|
||||
# Create user data
|
||||
user_create_data = {
|
||||
**user_data,
|
||||
'hashed_password': hashed_password,
|
||||
'is_active': True,
|
||||
'is_superuser': False
|
||||
}
|
||||
|
||||
return await self.user_repo.create(user_create_data)
|
||||
|
||||
async def change_password(self, user_id: int, new_password: str) -> bool:
|
||||
"""Change user password."""
|
||||
hashed_password = get_password_hash(new_password)
|
||||
|
||||
result = await self.user_repo.update(user_id, {
|
||||
'hashed_password': hashed_password
|
||||
})
|
||||
|
||||
return result is not None
|
||||
|
||||
async def activate_user(self, user_id: int) -> bool:
|
||||
"""Activate user account."""
|
||||
result = await self.user_repo.update(user_id, {'is_active': True})
|
||||
return result is not None
|
||||
|
||||
async def deactivate_user(self, user_id: int) -> bool:
|
||||
"""Deactivate user account."""
|
||||
result = await self.user_repo.update(user_id, {'is_active': False})
|
||||
return result is not None
|
||||
```
|
||||
|
||||
## Role-Based Access Control
|
||||
|
||||
```python
|
||||
# app/models/rbac.py
|
||||
from sqlalchemy import Column, String, Text, ForeignKey, Table
|
||||
from sqlalchemy.orm import relationship
|
||||
from app.models.base import BaseModel
|
||||
|
||||
# Association tables for many-to-many relationships
|
||||
user_roles = Table(
|
||||
'user_roles',
|
||||
Base.metadata,
|
||||
Column('user_id', Integer, ForeignKey('users.id')),
|
||||
Column('role_id', Integer, ForeignKey('roles.id'))
|
||||
)
|
||||
|
||||
role_permissions = Table(
|
||||
'role_permissions',
|
||||
Base.metadata,
|
||||
Column('role_id', Integer, ForeignKey('roles.id')),
|
||||
Column('permission_id', Integer, ForeignKey('permissions.id'))
|
||||
)
|
||||
|
||||
class Role(BaseModel):
|
||||
"""Role model for RBAC."""
|
||||
__tablename__ = "roles"
|
||||
|
||||
name = Column(String(50), unique=True, nullable=False, index=True)
|
||||
description = Column(Text)
|
||||
|
||||
# Relationships
|
||||
users = relationship("User", secondary=user_roles, back_populates="roles")
|
||||
permissions = relationship("Permission", secondary=role_permissions, back_populates="roles")
|
||||
|
||||
class Permission(BaseModel):
|
||||
"""Permission model for RBAC."""
|
||||
__tablename__ = "permissions"
|
||||
|
||||
name = Column(String(100), unique=True, nullable=False, index=True)
|
||||
description = Column(Text)
|
||||
resource = Column(String(50), nullable=False) # e.g., 'users', 'posts'
|
||||
action = Column(String(50), nullable=False) # e.g., 'create', 'read', 'update', 'delete'
|
||||
|
||||
# Relationships
|
||||
roles = relationship("Role", secondary=role_permissions, back_populates="permissions")
|
||||
|
||||
# Update User model to include roles
|
||||
class User(BaseModel):
|
||||
# ... existing fields ...
|
||||
|
||||
# Relationships
|
||||
roles = relationship("Role", secondary=user_roles, back_populates="users")
|
||||
|
||||
@property
|
||||
def permissions(self) -> list[str]:
|
||||
"""Get all permissions for user."""
|
||||
perms = set()
|
||||
for role in self.roles:
|
||||
for permission in role.permissions:
|
||||
perms.add(f"{permission.resource}:{permission.action}")
|
||||
return list(perms)
|
||||
```
|
||||
|
||||
## OAuth2 Integration
|
||||
|
||||
```python
|
||||
# app/api/v1/oauth.py
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from fastapi.security.utils import get_authorization_scheme_param
|
||||
from starlette.requests import Request
|
||||
from authlib.integrations.starlette_client import OAuth
|
||||
from app.core.config import settings
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
# OAuth configuration
|
||||
oauth = OAuth()
|
||||
|
||||
# Google OAuth
|
||||
google = oauth.register(
|
||||
name='google',
|
||||
client_id=settings.GOOGLE_CLIENT_ID,
|
||||
client_secret=settings.GOOGLE_CLIENT_SECRET,
|
||||
server_metadata_url='https://accounts.google.com/.well-known/openid_configuration',
|
||||
client_kwargs={
|
||||
'scope': 'openid email profile'
|
||||
}
|
||||
)
|
||||
|
||||
# GitHub OAuth
|
||||
github = oauth.register(
|
||||
name='github',
|
||||
client_id=settings.GITHUB_CLIENT_ID,
|
||||
client_secret=settings.GITHUB_CLIENT_SECRET,
|
||||
access_token_url='https://github.com/login/oauth/access_token',
|
||||
access_token_params=None,
|
||||
authorize_url='https://github.com/login/oauth/authorize',
|
||||
authorize_params=None,
|
||||
api_base_url='https://api.github.com/',
|
||||
client_kwargs={'scope': 'user:email'},
|
||||
)
|
||||
|
||||
@router.get('/google')
|
||||
async def google_login(request: Request):
|
||||
"""Initiate Google OAuth login."""
|
||||
redirect_uri = request.url_for('google_callback')
|
||||
return await google.authorize_redirect(request, redirect_uri)
|
||||
|
||||
@router.get('/google/callback')
|
||||
async def google_callback(request: Request):
|
||||
"""Handle Google OAuth callback."""
|
||||
token = await google.authorize_access_token(request)
|
||||
user_info = token.get('userinfo')
|
||||
|
||||
if user_info:
|
||||
# Create or get user
|
||||
# Generate JWT token
|
||||
# Return token
|
||||
pass
|
||||
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="OAuth authentication failed"
|
||||
)
|
||||
```
|
||||
|
||||
## API Key Authentication
|
||||
|
||||
```python
|
||||
# app/models/api_key.py
|
||||
from sqlalchemy import Column, String, Boolean, ForeignKey, DateTime
|
||||
from sqlalchemy.orm import relationship
|
||||
from app.models.base import BaseModel
|
||||
import secrets
|
||||
|
||||
class APIKey(BaseModel):
|
||||
"""API Key model."""
|
||||
__tablename__ = "api_keys"
|
||||
|
||||
name = Column(String(100), nullable=False)
|
||||
key_hash = Column(String(255), unique=True, nullable=False, index=True)
|
||||
prefix = Column(String(10), nullable=False, index=True)
|
||||
is_active = Column(Boolean, default=True, nullable=False)
|
||||
expires_at = Column(DateTime)
|
||||
last_used_at = Column(DateTime)
|
||||
|
||||
# Foreign key
|
||||
user_id = Column(Integer, ForeignKey("users.id"), nullable=False)
|
||||
|
||||
# Relationships
|
||||
user = relationship("User", back_populates="api_keys")
|
||||
|
||||
@classmethod
|
||||
def generate_key(cls) -> tuple[str, str]:
|
||||
"""Generate API key and return (key, hash)."""
|
||||
key = secrets.token_urlsafe(32)
|
||||
prefix = key[:8]
|
||||
key_hash = get_password_hash(key)
|
||||
return key, prefix, key_hash
|
||||
|
||||
def verify_key(self, key: str) -> bool:
|
||||
"""Verify API key."""
|
||||
return verify_password(key, self.key_hash)
|
||||
|
||||
# Add to User model
|
||||
class User(BaseModel):
|
||||
# ... existing fields ...
|
||||
|
||||
# Relationships
|
||||
api_keys = relationship("APIKey", back_populates="user", cascade="all, delete-orphan")
|
||||
```
|
||||
|
||||
## Testing Authentication
|
||||
|
||||
```python
|
||||
# tests/test_auth.py
|
||||
import pytest
|
||||
from httpx import AsyncClient
|
||||
from app.core.security import create_access_token
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_register_user(client: AsyncClient):
|
||||
"""Test user registration."""
|
||||
user_data = {
|
||||
"username": "testuser",
|
||||
"email": "test@example.com",
|
||||
"password": "testpass123",
|
||||
"first_name": "Test",
|
||||
"last_name": "User"
|
||||
}
|
||||
|
||||
response = await client.post("/api/v1/auth/register", json=user_data)
|
||||
assert response.status_code == 201
|
||||
|
||||
data = response.json()
|
||||
assert data["username"] == user_data["username"]
|
||||
assert data["email"] == user_data["email"]
|
||||
assert "hashed_password" not in data
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_login_user(client: AsyncClient, test_user):
|
||||
"""Test user login."""
|
||||
login_data = {
|
||||
"username": test_user.username,
|
||||
"password": "testpass123"
|
||||
}
|
||||
|
||||
response = await client.post(
|
||||
"/api/v1/auth/login",
|
||||
data=login_data,
|
||||
headers={"Content-Type": "application/x-www-form-urlencoded"}
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
|
||||
data = response.json()
|
||||
assert "access_token" in data
|
||||
assert "refresh_token" in data
|
||||
assert data["token_type"] == "bearer"
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_current_user(client: AsyncClient, test_user):
|
||||
"""Test get current user endpoint."""
|
||||
token = create_access_token(subject=test_user.id)
|
||||
headers = {"Authorization": f"Bearer {token}"}
|
||||
|
||||
response = await client.get("/api/v1/auth/me", headers=headers)
|
||||
assert response.status_code == 200
|
||||
|
||||
data = response.json()
|
||||
assert data["username"] == test_user.username
|
||||
assert data["email"] == test_user.email
|
||||
```
|
||||
@@ -0,0 +1,657 @@
|
||||
# FastAPI Database Integration
|
||||
|
||||
Complete database setup with SQLAlchemy, Alembic, and async support for FastAPI.
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
# Initialize Alembic
|
||||
alembic init alembic
|
||||
|
||||
# Create migration
|
||||
alembic revision --autogenerate -m "Initial migration"
|
||||
|
||||
# Apply migrations
|
||||
alembic upgrade head
|
||||
|
||||
# Downgrade migration
|
||||
alembic downgrade -1
|
||||
```
|
||||
|
||||
## Database Configuration
|
||||
|
||||
```python
|
||||
# app/core/config.py
|
||||
from pydantic import BaseSettings, PostgresDsn, validator
|
||||
from typing import Optional, Dict, Any
|
||||
import os
|
||||
|
||||
class Settings(BaseSettings):
|
||||
"""Application settings."""
|
||||
|
||||
# Database
|
||||
POSTGRES_SERVER: str = "localhost"
|
||||
POSTGRES_USER: str = "postgres"
|
||||
POSTGRES_PASSWORD: str = "password"
|
||||
POSTGRES_DB: str = "fastapi_app"
|
||||
POSTGRES_PORT: str = "5432"
|
||||
DATABASE_URL: Optional[PostgresDsn] = None
|
||||
|
||||
@validator("DATABASE_URL", pre=True)
|
||||
def assemble_db_connection(cls, v: Optional[str], values: Dict[str, Any]) -> Any:
|
||||
if isinstance(v, str):
|
||||
return v
|
||||
return PostgresDsn.build(
|
||||
scheme="postgresql+asyncpg",
|
||||
user=values.get("POSTGRES_USER"),
|
||||
password=values.get("POSTGRES_PASSWORD"),
|
||||
host=values.get("POSTGRES_SERVER"),
|
||||
port=values.get("POSTGRES_PORT"),
|
||||
path=f"/{values.get('POSTGRES_DB') or ''}",
|
||||
)
|
||||
|
||||
# Redis
|
||||
REDIS_URL: str = "redis://localhost:6379/0"
|
||||
|
||||
# Database settings
|
||||
DATABASE_POOL_SIZE: int = 10
|
||||
DATABASE_MAX_OVERFLOW: int = 20
|
||||
DATABASE_POOL_RECYCLE: int = 3600
|
||||
|
||||
class Config:
|
||||
env_file = ".env"
|
||||
case_sensitive = True
|
||||
|
||||
settings = Settings()
|
||||
```
|
||||
|
||||
## Database Setup
|
||||
|
||||
```python
|
||||
# app/db/database.py
|
||||
from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
from app.core.config import settings
|
||||
|
||||
# Create async engine
|
||||
engine = create_async_engine(
|
||||
str(settings.DATABASE_URL),
|
||||
pool_size=settings.DATABASE_POOL_SIZE,
|
||||
max_overflow=settings.DATABASE_MAX_OVERFLOW,
|
||||
pool_recycle=settings.DATABASE_POOL_RECYCLE,
|
||||
pool_pre_ping=True,
|
||||
echo=False # Set to True for SQL debugging
|
||||
)
|
||||
|
||||
# Create async session factory
|
||||
AsyncSessionLocal = sessionmaker(
|
||||
engine, class_=AsyncSession, expire_on_commit=False
|
||||
)
|
||||
|
||||
# Base class for models
|
||||
Base = declarative_base()
|
||||
|
||||
async def get_db() -> AsyncSession:
|
||||
"""Dependency to get database session."""
|
||||
async with AsyncSessionLocal() as session:
|
||||
try:
|
||||
yield session
|
||||
finally:
|
||||
await session.close()
|
||||
|
||||
async def create_tables():
|
||||
"""Create database tables."""
|
||||
async with engine.begin() as conn:
|
||||
await conn.run_sync(Base.metadata.create_all)
|
||||
|
||||
async def drop_tables():
|
||||
"""Drop database tables."""
|
||||
async with engine.begin() as conn:
|
||||
await conn.run_sync(Base.metadata.drop_all)
|
||||
```
|
||||
|
||||
## Base Model
|
||||
|
||||
```python
|
||||
# app/models/base.py
|
||||
from sqlalchemy import Column, Integer, DateTime, func
|
||||
from sqlalchemy.ext.declarative import declared_attr
|
||||
from app.db.database import Base
|
||||
from datetime import datetime
|
||||
from typing import Any
|
||||
|
||||
class TimestampMixin:
|
||||
"""Mixin for timestamp fields."""
|
||||
created_at = Column(DateTime(timezone=True), server_default=func.now(), nullable=False)
|
||||
updated_at = Column(
|
||||
DateTime(timezone=True),
|
||||
server_default=func.now(),
|
||||
onupdate=func.now(),
|
||||
nullable=False
|
||||
)
|
||||
|
||||
class BaseModel(Base, TimestampMixin):
|
||||
"""Base model with common functionality."""
|
||||
__abstract__ = True
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
|
||||
@declared_attr
|
||||
def __tablename__(cls) -> str:
|
||||
return cls.__name__.lower()
|
||||
|
||||
def dict(self, exclude: set = None) -> dict[str, Any]:
|
||||
"""Convert model to dictionary."""
|
||||
exclude = exclude or set()
|
||||
return {
|
||||
column.name: getattr(self, column.name)
|
||||
for column in self.__table__.columns
|
||||
if column.name not in exclude
|
||||
}
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"<{self.__class__.__name__}(id={self.id})>"
|
||||
```
|
||||
|
||||
## Example Models
|
||||
|
||||
```python
|
||||
# app/models/user.py
|
||||
from sqlalchemy import Column, String, Boolean, Text, Index
|
||||
from sqlalchemy.orm import relationship
|
||||
from app.models.base import BaseModel
|
||||
from passlib.context import CryptContext
|
||||
|
||||
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
|
||||
|
||||
class User(BaseModel):
|
||||
"""User model."""
|
||||
__tablename__ = "users"
|
||||
|
||||
username = Column(String(50), unique=True, index=True, nullable=False)
|
||||
email = Column(String(100), unique=True, index=True, nullable=False)
|
||||
hashed_password = Column(String(255), nullable=False)
|
||||
first_name = Column(String(50), nullable=False)
|
||||
last_name = Column(String(50), nullable=False)
|
||||
is_active = Column(Boolean, default=True, nullable=False)
|
||||
is_superuser = Column(Boolean, default=False, nullable=False)
|
||||
bio = Column(Text)
|
||||
|
||||
# Relationships
|
||||
posts = relationship("Post", back_populates="author", cascade="all, delete-orphan")
|
||||
|
||||
# Indexes
|
||||
__table_args__ = (
|
||||
Index('idx_user_email_active', email, is_active),
|
||||
Index('idx_user_username_active', username, is_active),
|
||||
)
|
||||
|
||||
def verify_password(self, password: str) -> bool:
|
||||
"""Verify password against hash."""
|
||||
return pwd_context.verify(password, self.hashed_password)
|
||||
|
||||
@staticmethod
|
||||
def get_password_hash(password: str) -> str:
|
||||
"""Generate password hash."""
|
||||
return pwd_context.hash(password)
|
||||
|
||||
def set_password(self, password: str) -> None:
|
||||
"""Set user password."""
|
||||
self.hashed_password = self.get_password_hash(password)
|
||||
|
||||
@property
|
||||
def full_name(self) -> str:
|
||||
"""Get user's full name."""
|
||||
return f"{self.first_name} {self.last_name}"
|
||||
|
||||
def dict(self, exclude: set = None) -> dict:
|
||||
"""Convert to dict excluding sensitive data."""
|
||||
exclude = exclude or set()
|
||||
exclude.add('hashed_password')
|
||||
return super().dict(exclude=exclude)
|
||||
|
||||
# app/models/post.py
|
||||
from sqlalchemy import Column, String, Text, ForeignKey, Boolean, Index
|
||||
from sqlalchemy.orm import relationship
|
||||
from app.models.base import BaseModel
|
||||
|
||||
class Post(BaseModel):
|
||||
"""Blog post model."""
|
||||
__tablename__ = "posts"
|
||||
|
||||
title = Column(String(200), nullable=False, index=True)
|
||||
content = Column(Text, nullable=False)
|
||||
slug = Column(String(200), unique=True, nullable=False, index=True)
|
||||
is_published = Column(Boolean, default=False, nullable=False)
|
||||
|
||||
# Foreign keys
|
||||
author_id = Column(Integer, ForeignKey("users.id"), nullable=False)
|
||||
|
||||
# Relationships
|
||||
author = relationship("User", back_populates="posts")
|
||||
|
||||
# Indexes
|
||||
__table_args__ = (
|
||||
Index('idx_post_published_created', is_published, 'created_at'),
|
||||
Index('idx_post_author_published', author_id, is_published),
|
||||
)
|
||||
```
|
||||
|
||||
## Repository Pattern
|
||||
|
||||
```python
|
||||
# app/repositories/base.py
|
||||
from typing import Generic, TypeVar, Type, Optional, List, Dict, Any
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy import select, update, delete, func
|
||||
from sqlalchemy.orm import selectinload
|
||||
from app.models.base import BaseModel
|
||||
|
||||
ModelType = TypeVar("ModelType", bound=BaseModel)
|
||||
|
||||
class BaseRepository(Generic[ModelType]):
|
||||
"""Base repository with common CRUD operations."""
|
||||
|
||||
def __init__(self, model: Type[ModelType], db: AsyncSession):
|
||||
self.model = model
|
||||
self.db = db
|
||||
|
||||
async def get(self, id: int) -> Optional[ModelType]:
|
||||
"""Get model by ID."""
|
||||
result = await self.db.execute(
|
||||
select(self.model).where(self.model.id == id)
|
||||
)
|
||||
return result.scalar_one_or_none()
|
||||
|
||||
async def get_multi(
|
||||
self,
|
||||
skip: int = 0,
|
||||
limit: int = 100,
|
||||
filters: Dict[str, Any] = None
|
||||
) -> List[ModelType]:
|
||||
"""Get multiple models with pagination."""
|
||||
query = select(self.model)
|
||||
|
||||
if filters:
|
||||
for field, value in filters.items():
|
||||
if hasattr(self.model, field):
|
||||
query = query.where(getattr(self.model, field) == value)
|
||||
|
||||
query = query.offset(skip).limit(limit)
|
||||
result = await self.db.execute(query)
|
||||
return result.scalars().all()
|
||||
|
||||
async def create(self, obj_in: Dict[str, Any]) -> ModelType:
|
||||
"""Create new model."""
|
||||
db_obj = self.model(**obj_in)
|
||||
self.db.add(db_obj)
|
||||
await self.db.commit()
|
||||
await self.db.refresh(db_obj)
|
||||
return db_obj
|
||||
|
||||
async def update(
|
||||
self,
|
||||
id: int,
|
||||
obj_in: Dict[str, Any]
|
||||
) -> Optional[ModelType]:
|
||||
"""Update model by ID."""
|
||||
await self.db.execute(
|
||||
update(self.model)
|
||||
.where(self.model.id == id)
|
||||
.values(**obj_in)
|
||||
)
|
||||
await self.db.commit()
|
||||
return await self.get(id)
|
||||
|
||||
async def delete(self, id: int) -> bool:
|
||||
"""Delete model by ID."""
|
||||
result = await self.db.execute(
|
||||
delete(self.model).where(self.model.id == id)
|
||||
)
|
||||
await self.db.commit()
|
||||
return result.rowcount > 0
|
||||
|
||||
async def count(self, filters: Dict[str, Any] = None) -> int:
|
||||
"""Count models with optional filters."""
|
||||
query = select(func.count(self.model.id))
|
||||
|
||||
if filters:
|
||||
for field, value in filters.items():
|
||||
if hasattr(self.model, field):
|
||||
query = query.where(getattr(self.model, field) == value)
|
||||
|
||||
result = await self.db.execute(query)
|
||||
return result.scalar()
|
||||
|
||||
# app/repositories/user.py
|
||||
from typing import Optional
|
||||
from sqlalchemy import select
|
||||
from app.models.user import User
|
||||
from app.repositories.base import BaseRepository
|
||||
|
||||
class UserRepository(BaseRepository[User]):
|
||||
"""User repository with custom methods."""
|
||||
|
||||
async def get_by_email(self, email: str) -> Optional[User]:
|
||||
"""Get user by email."""
|
||||
result = await self.db.execute(
|
||||
select(User).where(User.email == email)
|
||||
)
|
||||
return result.scalar_one_or_none()
|
||||
|
||||
async def get_by_username(self, username: str) -> Optional[User]:
|
||||
"""Get user by username."""
|
||||
result = await self.db.execute(
|
||||
select(User).where(User.username == username)
|
||||
)
|
||||
return result.scalar_one_or_none()
|
||||
|
||||
async def get_active_users(self, skip: int = 0, limit: int = 100):
|
||||
"""Get active users."""
|
||||
return await self.get_multi(
|
||||
skip=skip,
|
||||
limit=limit,
|
||||
filters={'is_active': True}
|
||||
)
|
||||
```
|
||||
|
||||
## Alembic Configuration
|
||||
|
||||
```python
|
||||
# alembic/env.py
|
||||
from logging.config import fileConfig
|
||||
from sqlalchemy import engine_from_config, pool
|
||||
from sqlalchemy.ext.asyncio import AsyncEngine
|
||||
from alembic import context
|
||||
import asyncio
|
||||
|
||||
# Import your models
|
||||
from app.models.base import Base
|
||||
from app.models.user import User
|
||||
from app.models.post import Post
|
||||
from app.core.config import settings
|
||||
|
||||
# Alembic Config object
|
||||
config = context.config
|
||||
|
||||
# Override database URL
|
||||
config.set_main_option("sqlalchemy.url", str(settings.DATABASE_URL))
|
||||
|
||||
# Interpret the config file for logging
|
||||
if config.config_file_name is not None:
|
||||
fileConfig(config.config_file_name)
|
||||
|
||||
# Add your model's MetaData object here
|
||||
target_metadata = Base.metadata
|
||||
|
||||
def do_run_migrations(connection):
|
||||
context.configure(
|
||||
connection=connection,
|
||||
target_metadata=target_metadata,
|
||||
compare_type=True,
|
||||
compare_server_default=True,
|
||||
)
|
||||
|
||||
with context.begin_transaction():
|
||||
context.run_migrations()
|
||||
|
||||
async def run_migrations_online():
|
||||
"""Run migrations in 'online' mode."""
|
||||
configuration = config.get_section(config.config_ini_section)
|
||||
configuration["sqlalchemy.url"] = str(settings.DATABASE_URL)
|
||||
|
||||
connectable = AsyncEngine(
|
||||
engine_from_config(
|
||||
configuration,
|
||||
prefix="sqlalchemy.",
|
||||
poolclass=pool.NullPool,
|
||||
)
|
||||
)
|
||||
|
||||
async with connectable.connect() as connection:
|
||||
await connection.run_sync(do_run_migrations)
|
||||
|
||||
await connectable.dispose()
|
||||
|
||||
if context.is_offline_mode():
|
||||
run_migrations_offline()
|
||||
else:
|
||||
asyncio.run(run_migrations_online())
|
||||
```
|
||||
|
||||
## Database Utilities
|
||||
|
||||
```python
|
||||
# app/db/utils.py
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy import text
|
||||
from app.db.database import engine, AsyncSessionLocal
|
||||
from app.core.config import settings
|
||||
import asyncio
|
||||
|
||||
async def check_database_connection() -> bool:
|
||||
"""Check if database is accessible."""
|
||||
try:
|
||||
async with AsyncSessionLocal() as session:
|
||||
await session.execute(text("SELECT 1"))
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
async def create_database_if_not_exists():
|
||||
"""Create database if it doesn't exist."""
|
||||
# This is PostgreSQL specific
|
||||
import asyncpg
|
||||
from urllib.parse import urlparse
|
||||
|
||||
url = urlparse(str(settings.DATABASE_URL))
|
||||
|
||||
try:
|
||||
# Connect to postgres database to create our database
|
||||
conn = await asyncpg.connect(
|
||||
host=url.hostname,
|
||||
port=url.port,
|
||||
user=url.username,
|
||||
password=url.password,
|
||||
database='postgres'
|
||||
)
|
||||
|
||||
# Check if database exists
|
||||
exists = await conn.fetchval(
|
||||
"SELECT 1 FROM pg_database WHERE datname = $1",
|
||||
url.path[1:] # Remove leading slash
|
||||
)
|
||||
|
||||
if not exists:
|
||||
await conn.execute(f'CREATE DATABASE "{url.path[1:]}"')
|
||||
print(f"Database {url.path[1:]} created.")
|
||||
|
||||
await conn.close()
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error creating database: {e}")
|
||||
|
||||
async def execute_raw_sql(sql: str, params: dict = None) -> list:
|
||||
"""Execute raw SQL query."""
|
||||
async with AsyncSessionLocal() as session:
|
||||
result = await session.execute(text(sql), params or {})
|
||||
return result.fetchall()
|
||||
|
||||
async def get_table_info(table_name: str) -> dict:
|
||||
"""Get information about a table."""
|
||||
sql = """
|
||||
SELECT
|
||||
column_name,
|
||||
data_type,
|
||||
is_nullable,
|
||||
column_default
|
||||
FROM information_schema.columns
|
||||
WHERE table_name = :table_name
|
||||
ORDER BY ordinal_position;
|
||||
"""
|
||||
|
||||
result = await execute_raw_sql(sql, {'table_name': table_name})
|
||||
return [
|
||||
{
|
||||
'column_name': row[0],
|
||||
'data_type': row[1],
|
||||
'is_nullable': row[2],
|
||||
'column_default': row[3]
|
||||
}
|
||||
for row in result
|
||||
]
|
||||
```
|
||||
|
||||
## Database Initialization
|
||||
|
||||
```python
|
||||
# app/db/init_db.py
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from app.db.database import get_db, create_tables
|
||||
from app.models.user import User
|
||||
from app.repositories.user import UserRepository
|
||||
from app.core.config import settings
|
||||
import asyncio
|
||||
|
||||
async def init_db() -> None:
|
||||
"""Initialize database with tables and default data."""
|
||||
# Create tables
|
||||
await create_tables()
|
||||
print("Database tables created.")
|
||||
|
||||
# Create default superuser
|
||||
async with AsyncSessionLocal() as session:
|
||||
user_repo = UserRepository(User, session)
|
||||
|
||||
# Check if superuser exists
|
||||
existing_user = await user_repo.get_by_email("admin@example.com")
|
||||
|
||||
if not existing_user:
|
||||
superuser_data = {
|
||||
"username": "admin",
|
||||
"email": "admin@example.com",
|
||||
"first_name": "Admin",
|
||||
"last_name": "User",
|
||||
"is_superuser": True,
|
||||
"is_active": True
|
||||
}
|
||||
|
||||
superuser = User(**superuser_data)
|
||||
superuser.set_password("admin123")
|
||||
|
||||
session.add(superuser)
|
||||
await session.commit()
|
||||
print("Superuser created.")
|
||||
else:
|
||||
print("Superuser already exists.")
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(init_db())
|
||||
```
|
||||
|
||||
## Testing Database
|
||||
|
||||
```python
|
||||
# tests/conftest.py
|
||||
import pytest
|
||||
import asyncio
|
||||
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
from app.db.database import Base, get_db
|
||||
from app.main import app
|
||||
import pytest_asyncio
|
||||
|
||||
# Test database URL
|
||||
TEST_DATABASE_URL = "sqlite+aiosqlite:///./test.db"
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def event_loop():
|
||||
"""Create event loop for async tests."""
|
||||
loop = asyncio.get_event_loop_policy().new_event_loop()
|
||||
yield loop
|
||||
loop.close()
|
||||
|
||||
@pytest_asyncio.fixture(scope="session")
|
||||
async def test_engine():
|
||||
"""Create test database engine."""
|
||||
engine = create_async_engine(TEST_DATABASE_URL, echo=False)
|
||||
|
||||
# Create tables
|
||||
async with engine.begin() as conn:
|
||||
await conn.run_sync(Base.metadata.create_all)
|
||||
|
||||
yield engine
|
||||
|
||||
# Drop tables
|
||||
async with engine.begin() as conn:
|
||||
await conn.run_sync(Base.metadata.drop_all)
|
||||
|
||||
await engine.dispose()
|
||||
|
||||
@pytest_asyncio.fixture
|
||||
async def test_session(test_engine):
|
||||
"""Create test database session."""
|
||||
TestSessionLocal = sessionmaker(
|
||||
test_engine, class_=AsyncSession, expire_on_commit=False
|
||||
)
|
||||
|
||||
async with TestSessionLocal() as session:
|
||||
yield session
|
||||
|
||||
@pytest.fixture
|
||||
def override_get_db(test_session):
|
||||
"""Override database dependency."""
|
||||
async def _override_get_db():
|
||||
yield test_session
|
||||
|
||||
app.dependency_overrides[get_db] = _override_get_db
|
||||
yield
|
||||
app.dependency_overrides = {}
|
||||
```
|
||||
|
||||
## Database Health Check
|
||||
|
||||
```python
|
||||
# app/api/health.py
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy import text
|
||||
from app.db.database import get_db
|
||||
import time
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
@router.get("/health")
|
||||
async def health_check(db: AsyncSession = Depends(get_db)):
|
||||
"""Health check endpoint."""
|
||||
checks = {
|
||||
"status": "healthy",
|
||||
"timestamp": time.time(),
|
||||
"database": await check_database_health(db),
|
||||
}
|
||||
|
||||
# Determine overall status
|
||||
if checks["database"]["status"] != "ok":
|
||||
checks["status"] = "unhealthy"
|
||||
raise HTTPException(status_code=503, detail=checks)
|
||||
|
||||
return checks
|
||||
|
||||
async def check_database_health(db: AsyncSession) -> dict:
|
||||
"""Check database connection."""
|
||||
try:
|
||||
start_time = time.time()
|
||||
await db.execute(text("SELECT 1"))
|
||||
response_time = (time.time() - start_time) * 1000 # milliseconds
|
||||
|
||||
return {
|
||||
"status": "ok",
|
||||
"response_time_ms": round(response_time, 2)
|
||||
}
|
||||
except Exception as e:
|
||||
return {
|
||||
"status": "error",
|
||||
"error": str(e)
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,160 @@
|
||||
# FastAPI Deployment
|
||||
|
||||
Basic production deployment setup for FastAPI applications.
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
# Run with Uvicorn
|
||||
uvicorn app.main:app --host 0.0.0.0 --port 8000 --workers 4
|
||||
|
||||
# Docker deployment
|
||||
docker build -t fastapi-app .
|
||||
docker run -p 8000:8000 fastapi-app
|
||||
```
|
||||
|
||||
## Production Configuration
|
||||
|
||||
```python
|
||||
# app/core/config.py
|
||||
from pydantic import BaseSettings
|
||||
import os
|
||||
|
||||
class Settings(BaseSettings):
|
||||
"""Production settings."""
|
||||
|
||||
# App
|
||||
PROJECT_NAME: str = "FastAPI App"
|
||||
VERSION: str = "1.0.0"
|
||||
SECRET_KEY: str = os.getenv("SECRET_KEY", "dev-key")
|
||||
|
||||
# Server
|
||||
HOST: str = "0.0.0.0"
|
||||
PORT: int = 8000
|
||||
WORKERS: int = 4
|
||||
|
||||
# Database
|
||||
DATABASE_URL: str = os.getenv("DATABASE_URL", "sqlite:///./app.db")
|
||||
|
||||
# Redis
|
||||
REDIS_URL: str = os.getenv("REDIS_URL", "redis://localhost:6379")
|
||||
|
||||
class Config:
|
||||
env_file = ".env"
|
||||
|
||||
settings = Settings()
|
||||
```
|
||||
|
||||
## Docker Setup
|
||||
|
||||
```dockerfile
|
||||
# Dockerfile
|
||||
FROM python:3.11-slim
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Install dependencies
|
||||
COPY requirements.txt .
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
|
||||
# Copy app
|
||||
COPY . .
|
||||
|
||||
# Create non-root user
|
||||
RUN useradd -m appuser && chown -R appuser:appuser /app
|
||||
USER appuser
|
||||
|
||||
EXPOSE 8000
|
||||
|
||||
# Health check
|
||||
HEALTHCHECK CMD curl -f http://localhost:8000/health || exit 1
|
||||
|
||||
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
|
||||
```
|
||||
|
||||
## Docker Compose
|
||||
|
||||
```yaml
|
||||
# docker-compose.yml
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
app:
|
||||
build: .
|
||||
ports:
|
||||
- "8000:8000"
|
||||
environment:
|
||||
- DATABASE_URL=postgresql://user:pass@db:5432/fastapi_db
|
||||
- REDIS_URL=redis://redis:6379
|
||||
depends_on:
|
||||
- db
|
||||
- redis
|
||||
|
||||
db:
|
||||
image: postgres:15
|
||||
environment:
|
||||
POSTGRES_DB: fastapi_db
|
||||
POSTGRES_USER: user
|
||||
POSTGRES_PASSWORD: password
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql/data
|
||||
|
||||
redis:
|
||||
image: redis:7-alpine
|
||||
volumes:
|
||||
- redis_data:/data
|
||||
|
||||
volumes:
|
||||
postgres_data:
|
||||
redis_data:
|
||||
```
|
||||
|
||||
## Environment Variables
|
||||
|
||||
```bash
|
||||
# .env
|
||||
SECRET_KEY=your-secret-key
|
||||
DATABASE_URL=postgresql://user:pass@localhost/fastapi_db
|
||||
REDIS_URL=redis://localhost:6379
|
||||
```
|
||||
|
||||
## Health Check
|
||||
|
||||
```python
|
||||
# app/api/health.py
|
||||
from fastapi import APIRouter
|
||||
from app.core.config import settings
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
@router.get("/health")
|
||||
async def health_check():
|
||||
return {
|
||||
"status": "healthy",
|
||||
"version": settings.VERSION
|
||||
}
|
||||
```
|
||||
|
||||
## Deployment Script
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# deploy.sh
|
||||
|
||||
set -e
|
||||
|
||||
echo "Deploying FastAPI app..."
|
||||
|
||||
# Build and deploy
|
||||
docker-compose build
|
||||
docker-compose up -d
|
||||
|
||||
# Health check
|
||||
echo "Checking health..."
|
||||
if curl -f http://localhost:8000/health; then
|
||||
echo "✅ Deployment successful!"
|
||||
else
|
||||
echo "❌ Deployment failed!"
|
||||
exit 1
|
||||
fi
|
||||
```
|
||||
@@ -0,0 +1,927 @@
|
||||
# FastAPI Testing Framework
|
||||
|
||||
Comprehensive testing setup for FastAPI applications with pytest and async support.
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
# Run all tests
|
||||
pytest
|
||||
|
||||
# Run with coverage
|
||||
pytest --cov=app --cov-report=html
|
||||
|
||||
# Run specific test file
|
||||
pytest tests/test_api.py
|
||||
|
||||
# Run with verbose output
|
||||
pytest -v -s
|
||||
```
|
||||
|
||||
## Test Configuration
|
||||
|
||||
```python
|
||||
# pytest.ini
|
||||
[tool:pytest]
|
||||
testpaths = tests
|
||||
python_files = test_*.py
|
||||
python_classes = Test*
|
||||
python_functions = test_*
|
||||
addopts =
|
||||
--cov=app
|
||||
--cov-report=term-missing
|
||||
--cov-report=html:htmlcov
|
||||
--asyncio-mode=auto
|
||||
--strict-markers
|
||||
--disable-warnings
|
||||
markers =
|
||||
unit: Unit tests
|
||||
integration: Integration tests
|
||||
e2e: End-to-end tests
|
||||
slow: Slow running tests
|
||||
auth: Authentication tests
|
||||
api: API tests
|
||||
asyncio_mode = auto
|
||||
```
|
||||
|
||||
## Test Dependencies
|
||||
|
||||
```python
|
||||
# requirements/test.txt
|
||||
pytest>=7.0.0
|
||||
pytest-asyncio>=0.21.0
|
||||
pytest-cov>=4.0.0
|
||||
httpx>=0.24.0
|
||||
factory-boy>=3.2.0
|
||||
faker>=18.0.0
|
||||
respx>=0.20.0
|
||||
pytest-mock>=3.10.0
|
||||
```
|
||||
|
||||
## Test Fixtures
|
||||
|
||||
```python
|
||||
# tests/conftest.py
|
||||
import pytest
|
||||
import asyncio
|
||||
from typing import AsyncGenerator, Generator
|
||||
from fastapi.testclient import TestClient
|
||||
from httpx import AsyncClient
|
||||
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
from app.main import app
|
||||
from app.db.database import get_db, Base
|
||||
from app.models.user import User
|
||||
from app.core.security import get_password_hash
|
||||
from tests.factories import UserFactory
|
||||
|
||||
# Test database URL
|
||||
TEST_DATABASE_URL = "sqlite+aiosqlite:///./test.db"
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def event_loop() -> Generator[asyncio.AbstractEventLoop, None, None]:
|
||||
"""Create event loop for the test session."""
|
||||
loop = asyncio.get_event_loop_policy().new_event_loop()
|
||||
yield loop
|
||||
loop.close()
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
async def test_engine():
|
||||
"""Create test database engine."""
|
||||
engine = create_async_engine(
|
||||
TEST_DATABASE_URL,
|
||||
echo=False,
|
||||
future=True
|
||||
)
|
||||
|
||||
# Create tables
|
||||
async with engine.begin() as conn:
|
||||
await conn.run_sync(Base.metadata.create_all)
|
||||
|
||||
yield engine
|
||||
|
||||
# Drop tables and dispose engine
|
||||
async with engine.begin() as conn:
|
||||
await conn.run_sync(Base.metadata.drop_all)
|
||||
|
||||
await engine.dispose()
|
||||
|
||||
@pytest.fixture
|
||||
async def db_session(test_engine) -> AsyncGenerator[AsyncSession, None]:
|
||||
"""Create database session for testing."""
|
||||
TestSessionLocal = sessionmaker(
|
||||
test_engine,
|
||||
class_=AsyncSession,
|
||||
expire_on_commit=False
|
||||
)
|
||||
|
||||
async with TestSessionLocal() as session:
|
||||
yield session
|
||||
|
||||
@pytest.fixture
|
||||
def override_get_db(db_session: AsyncSession) -> Generator:
|
||||
"""Override database dependency."""
|
||||
async def _override_get_db():
|
||||
yield db_session
|
||||
|
||||
app.dependency_overrides[get_db] = _override_get_db
|
||||
yield
|
||||
app.dependency_overrides = {}
|
||||
|
||||
@pytest.fixture
|
||||
def client(override_get_db) -> Generator[TestClient, None, None]:
|
||||
"""Create test client."""
|
||||
with TestClient(app) as test_client:
|
||||
yield test_client
|
||||
|
||||
@pytest.fixture
|
||||
async def async_client(override_get_db) -> AsyncGenerator[AsyncClient, None]:
|
||||
"""Create async test client."""
|
||||
async with AsyncClient(app=app, base_url="http://test") as ac:
|
||||
yield ac
|
||||
|
||||
@pytest.fixture
|
||||
async def test_user(db_session: AsyncSession) -> User:
|
||||
"""Create test user."""
|
||||
user_data = {
|
||||
"username": "testuser",
|
||||
"email": "test@example.com",
|
||||
"hashed_password": get_password_hash("testpass123"),
|
||||
"first_name": "Test",
|
||||
"last_name": "User",
|
||||
"is_active": True,
|
||||
"is_superuser": False
|
||||
}
|
||||
|
||||
user = User(**user_data)
|
||||
db_session.add(user)
|
||||
await db_session.commit()
|
||||
await db_session.refresh(user)
|
||||
return user
|
||||
|
||||
@pytest.fixture
|
||||
async def superuser(db_session: AsyncSession) -> User:
|
||||
"""Create superuser."""
|
||||
user_data = {
|
||||
"username": "admin",
|
||||
"email": "admin@example.com",
|
||||
"hashed_password": get_password_hash("adminpass123"),
|
||||
"first_name": "Admin",
|
||||
"last_name": "User",
|
||||
"is_active": True,
|
||||
"is_superuser": True
|
||||
}
|
||||
|
||||
user = User(**user_data)
|
||||
db_session.add(user)
|
||||
await db_session.commit()
|
||||
await db_session.refresh(user)
|
||||
return user
|
||||
|
||||
@pytest.fixture
|
||||
def user_token(test_user: User) -> str:
|
||||
"""Create authentication token for test user."""
|
||||
from app.core.security import create_access_token
|
||||
return create_access_token(subject=test_user.id)
|
||||
|
||||
@pytest.fixture
|
||||
def superuser_token(superuser: User) -> str:
|
||||
"""Create authentication token for superuser."""
|
||||
from app.core.security import create_access_token
|
||||
return create_access_token(subject=superuser.id)
|
||||
|
||||
@pytest.fixture
|
||||
def auth_headers(user_token: str) -> dict[str, str]:
|
||||
"""Create authorization headers."""
|
||||
return {"Authorization": f"Bearer {user_token}"}
|
||||
|
||||
@pytest.fixture
|
||||
def superuser_headers(superuser_token: str) -> dict[str, str]:
|
||||
"""Create superuser authorization headers."""
|
||||
return {"Authorization": f"Bearer {superuser_token}"}
|
||||
```
|
||||
|
||||
## Test Factories
|
||||
|
||||
```python
|
||||
# tests/factories.py
|
||||
import factory
|
||||
from factory import Faker, SubFactory
|
||||
from app.models.user import User
|
||||
from app.models.post import Post
|
||||
from app.core.security import get_password_hash
|
||||
|
||||
class UserFactory(factory.Factory):
|
||||
"""Factory for User model."""
|
||||
|
||||
class Meta:
|
||||
model = User
|
||||
|
||||
username = Faker('user_name')
|
||||
email = Faker('email')
|
||||
first_name = Faker('first_name')
|
||||
last_name = Faker('last_name')
|
||||
hashed_password = factory.LazyAttribute(lambda obj: get_password_hash('testpass123'))
|
||||
is_active = True
|
||||
is_superuser = False
|
||||
bio = Faker('text', max_nb_chars=200)
|
||||
|
||||
class SuperUserFactory(UserFactory):
|
||||
"""Factory for superuser."""
|
||||
username = 'admin'
|
||||
email = 'admin@example.com'
|
||||
first_name = 'Admin'
|
||||
last_name = 'User'
|
||||
is_superuser = True
|
||||
|
||||
class PostFactory(factory.Factory):
|
||||
"""Factory for Post model."""
|
||||
|
||||
class Meta:
|
||||
model = Post
|
||||
|
||||
title = Faker('sentence', nb_words=4)
|
||||
content = Faker('text', max_nb_chars=1000)
|
||||
slug = Faker('slug')
|
||||
is_published = True
|
||||
author = SubFactory(UserFactory)
|
||||
|
||||
class InactiveUserFactory(UserFactory):
|
||||
"""Factory for inactive user."""
|
||||
is_active = False
|
||||
```
|
||||
|
||||
## API Testing
|
||||
|
||||
```python
|
||||
# tests/test_api/test_users.py
|
||||
import pytest
|
||||
from httpx import AsyncClient
|
||||
from app.models.user import User
|
||||
from tests.factories import UserFactory
|
||||
|
||||
class TestUserAPI:
|
||||
"""Test User API endpoints."""
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_create_user(
|
||||
self,
|
||||
async_client: AsyncClient,
|
||||
superuser_headers: dict
|
||||
):
|
||||
"""Test POST /api/v1/users/."""
|
||||
user_data = {
|
||||
"username": "newuser",
|
||||
"email": "new@example.com",
|
||||
"password": "newpass123",
|
||||
"first_name": "New",
|
||||
"last_name": "User"
|
||||
}
|
||||
|
||||
response = await async_client.post(
|
||||
"/api/v1/users/",
|
||||
json=user_data,
|
||||
headers=superuser_headers
|
||||
)
|
||||
|
||||
assert response.status_code == 201
|
||||
data = response.json()
|
||||
assert data["username"] == user_data["username"]
|
||||
assert data["email"] == user_data["email"]
|
||||
assert "password" not in data
|
||||
assert "hashed_password" not in data
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_users(
|
||||
self,
|
||||
async_client: AsyncClient,
|
||||
test_user: User,
|
||||
auth_headers: dict
|
||||
):
|
||||
"""Test GET /api/v1/users/."""
|
||||
response = await async_client.get(
|
||||
"/api/v1/users/",
|
||||
headers=auth_headers
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert "items" in data
|
||||
assert "total" in data
|
||||
assert "page" in data
|
||||
assert "size" in data
|
||||
assert len(data["items"]) >= 1
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_user(
|
||||
self,
|
||||
async_client: AsyncClient,
|
||||
test_user: User,
|
||||
auth_headers: dict
|
||||
):
|
||||
"""Test GET /api/v1/users/{id}."""
|
||||
response = await async_client.get(
|
||||
f"/api/v1/users/{test_user.id}",
|
||||
headers=auth_headers
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["id"] == test_user.id
|
||||
assert data["username"] == test_user.username
|
||||
assert data["email"] == test_user.email
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_update_user(
|
||||
self,
|
||||
async_client: AsyncClient,
|
||||
test_user: User,
|
||||
auth_headers: dict
|
||||
):
|
||||
"""Test PUT /api/v1/users/{id}."""
|
||||
update_data = {
|
||||
"first_name": "Updated",
|
||||
"last_name": "Name",
|
||||
"bio": "Updated bio"
|
||||
}
|
||||
|
||||
response = await async_client.put(
|
||||
f"/api/v1/users/{test_user.id}",
|
||||
json=update_data,
|
||||
headers=auth_headers
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["first_name"] == update_data["first_name"]
|
||||
assert data["last_name"] == update_data["last_name"]
|
||||
assert data["bio"] == update_data["bio"]
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_delete_user(
|
||||
self,
|
||||
async_client: AsyncClient,
|
||||
test_user: User,
|
||||
superuser_headers: dict
|
||||
):
|
||||
"""Test DELETE /api/v1/users/{id}."""
|
||||
response = await async_client.delete(
|
||||
f"/api/v1/users/{test_user.id}",
|
||||
headers=superuser_headers
|
||||
)
|
||||
|
||||
assert response.status_code == 204
|
||||
|
||||
# Verify user is deleted
|
||||
get_response = await async_client.get(
|
||||
f"/api/v1/users/{test_user.id}",
|
||||
headers=superuser_headers
|
||||
)
|
||||
assert get_response.status_code == 404
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_unauthorized_access(self, async_client: AsyncClient):
|
||||
"""Test unauthorized access to protected endpoints."""
|
||||
response = await async_client.get("/api/v1/users/")
|
||||
assert response.status_code == 401
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_forbidden_access(
|
||||
self,
|
||||
async_client: AsyncClient,
|
||||
auth_headers: dict
|
||||
):
|
||||
"""Test forbidden access to admin endpoints."""
|
||||
user_data = {
|
||||
"username": "unauthorized",
|
||||
"email": "unauthorized@example.com",
|
||||
"password": "pass123"
|
||||
}
|
||||
|
||||
response = await async_client.post(
|
||||
"/api/v1/users/",
|
||||
json=user_data,
|
||||
headers=auth_headers # Regular user, not superuser
|
||||
)
|
||||
|
||||
assert response.status_code == 403
|
||||
```
|
||||
|
||||
## Authentication Testing
|
||||
|
||||
```python
|
||||
# tests/test_api/test_auth.py
|
||||
import pytest
|
||||
from httpx import AsyncClient
|
||||
from app.models.user import User
|
||||
from app.core.security import create_access_token, decode_token
|
||||
|
||||
class TestAuthAPI:
|
||||
"""Test authentication API endpoints."""
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_register(
|
||||
self,
|
||||
async_client: AsyncClient
|
||||
):
|
||||
"""Test user registration."""
|
||||
user_data = {
|
||||
"username": "newuser",
|
||||
"email": "new@example.com",
|
||||
"password": "newpass123",
|
||||
"first_name": "New",
|
||||
"last_name": "User"
|
||||
}
|
||||
|
||||
response = await async_client.post(
|
||||
"/api/v1/auth/register",
|
||||
json=user_data
|
||||
)
|
||||
|
||||
assert response.status_code == 201
|
||||
data = response.json()
|
||||
assert data["username"] == user_data["username"]
|
||||
assert data["email"] == user_data["email"]
|
||||
assert "password" not in data
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_register_duplicate_email(
|
||||
self,
|
||||
async_client: AsyncClient,
|
||||
test_user: User
|
||||
):
|
||||
"""Test registration with duplicate email."""
|
||||
user_data = {
|
||||
"username": "different",
|
||||
"email": test_user.email, # Duplicate email
|
||||
"password": "pass123",
|
||||
"first_name": "Test",
|
||||
"last_name": "User"
|
||||
}
|
||||
|
||||
response = await async_client.post(
|
||||
"/api/v1/auth/register",
|
||||
json=user_data
|
||||
)
|
||||
|
||||
assert response.status_code == 400
|
||||
assert "Email already registered" in response.json()["detail"]
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_login(
|
||||
self,
|
||||
async_client: AsyncClient,
|
||||
test_user: User
|
||||
):
|
||||
"""Test user login."""
|
||||
login_data = {
|
||||
"username": test_user.username,
|
||||
"password": "testpass123"
|
||||
}
|
||||
|
||||
response = await async_client.post(
|
||||
"/api/v1/auth/login",
|
||||
data=login_data,
|
||||
headers={"Content-Type": "application/x-www-form-urlencoded"}
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert "access_token" in data
|
||||
assert "refresh_token" in data
|
||||
assert data["token_type"] == "bearer"
|
||||
assert "expires_in" in data
|
||||
|
||||
# Verify token is valid
|
||||
payload = decode_token(data["access_token"])
|
||||
assert payload is not None
|
||||
assert payload["sub"] == str(test_user.id)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_login_invalid_credentials(
|
||||
self,
|
||||
async_client: AsyncClient,
|
||||
test_user: User
|
||||
):
|
||||
"""Test login with invalid credentials."""
|
||||
login_data = {
|
||||
"username": test_user.username,
|
||||
"password": "wrongpassword"
|
||||
}
|
||||
|
||||
response = await async_client.post(
|
||||
"/api/v1/auth/login",
|
||||
data=login_data,
|
||||
headers={"Content-Type": "application/x-www-form-urlencoded"}
|
||||
)
|
||||
|
||||
assert response.status_code == 401
|
||||
assert "Incorrect username or password" in response.json()["detail"]
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_current_user(
|
||||
self,
|
||||
async_client: AsyncClient,
|
||||
test_user: User,
|
||||
auth_headers: dict
|
||||
):
|
||||
"""Test get current user endpoint."""
|
||||
response = await async_client.get(
|
||||
"/api/v1/auth/me",
|
||||
headers=auth_headers
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["id"] == test_user.id
|
||||
assert data["username"] == test_user.username
|
||||
assert data["email"] == test_user.email
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_refresh_token(
|
||||
self,
|
||||
async_client: AsyncClient,
|
||||
test_user: User
|
||||
):
|
||||
"""Test token refresh."""
|
||||
from app.core.security import create_refresh_token
|
||||
|
||||
refresh_token = create_refresh_token(subject=test_user.id)
|
||||
|
||||
response = await async_client.post(
|
||||
"/api/v1/auth/refresh",
|
||||
json={"refresh_token": refresh_token}
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert "access_token" in data
|
||||
assert "refresh_token" in data
|
||||
assert data["token_type"] == "bearer"
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_change_password(
|
||||
self,
|
||||
async_client: AsyncClient,
|
||||
test_user: User,
|
||||
auth_headers: dict
|
||||
):
|
||||
"""Test password change."""
|
||||
password_data = {
|
||||
"current_password": "testpass123",
|
||||
"new_password": "newpass123"
|
||||
}
|
||||
|
||||
response = await async_client.post(
|
||||
"/api/v1/auth/change-password",
|
||||
json=password_data,
|
||||
headers=auth_headers
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
assert "Password changed successfully" in response.json()["message"]
|
||||
```
|
||||
|
||||
## Model Testing
|
||||
|
||||
```python
|
||||
# tests/test_models/test_user.py
|
||||
import pytest
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from app.models.user import User
|
||||
from app.core.security import verify_password, get_password_hash
|
||||
|
||||
class TestUserModel:
|
||||
"""Test User model."""
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_create_user(self, db_session: AsyncSession):
|
||||
"""Test user creation."""
|
||||
user_data = {
|
||||
"username": "testuser",
|
||||
"email": "test@example.com",
|
||||
"hashed_password": get_password_hash("password123"),
|
||||
"first_name": "Test",
|
||||
"last_name": "User"
|
||||
}
|
||||
|
||||
user = User(**user_data)
|
||||
db_session.add(user)
|
||||
await db_session.commit()
|
||||
await db_session.refresh(user)
|
||||
|
||||
assert user.id is not None
|
||||
assert user.username == "testuser"
|
||||
assert user.email == "test@example.com"
|
||||
assert user.full_name == "Test User"
|
||||
assert user.is_active is True
|
||||
assert user.is_superuser is False
|
||||
assert user.created_at is not None
|
||||
assert user.updated_at is not None
|
||||
|
||||
def test_password_verification(self):
|
||||
"""Test password verification."""
|
||||
password = "testpassword123"
|
||||
hashed = get_password_hash(password)
|
||||
|
||||
user = User(
|
||||
username="test",
|
||||
email="test@example.com",
|
||||
hashed_password=hashed,
|
||||
first_name="Test",
|
||||
last_name="User"
|
||||
)
|
||||
|
||||
assert user.verify_password(password)
|
||||
assert not user.verify_password("wrongpassword")
|
||||
|
||||
def test_user_dict_excludes_password(self):
|
||||
"""Test that dict() method excludes password."""
|
||||
user = User(
|
||||
username="test",
|
||||
email="test@example.com",
|
||||
hashed_password="hashed_password",
|
||||
first_name="Test",
|
||||
last_name="User"
|
||||
)
|
||||
|
||||
user_dict = user.dict()
|
||||
assert "hashed_password" not in user_dict
|
||||
assert "username" in user_dict
|
||||
assert "email" in user_dict
|
||||
|
||||
def test_user_repr(self):
|
||||
"""Test user string representation."""
|
||||
user = User(
|
||||
id=1,
|
||||
username="test",
|
||||
email="test@example.com",
|
||||
hashed_password="hash",
|
||||
first_name="Test",
|
||||
last_name="User"
|
||||
)
|
||||
|
||||
assert repr(user) == "<User(id=1)>"
|
||||
```
|
||||
|
||||
## Repository Testing
|
||||
|
||||
```python
|
||||
# tests/test_repositories/test_user.py
|
||||
import pytest
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from app.models.user import User
|
||||
from app.repositories.user import UserRepository
|
||||
from app.core.security import get_password_hash
|
||||
|
||||
class TestUserRepository:
|
||||
"""Test UserRepository."""
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_create_user(self, db_session: AsyncSession):
|
||||
"""Test user creation through repository."""
|
||||
repo = UserRepository(User, db_session)
|
||||
|
||||
user_data = {
|
||||
"username": "repouser",
|
||||
"email": "repo@example.com",
|
||||
"hashed_password": get_password_hash("password123"),
|
||||
"first_name": "Repo",
|
||||
"last_name": "User"
|
||||
}
|
||||
|
||||
user = await repo.create(user_data)
|
||||
|
||||
assert user.id is not None
|
||||
assert user.username == "repouser"
|
||||
assert user.email == "repo@example.com"
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_by_email(self, db_session: AsyncSession, test_user: User):
|
||||
"""Test get user by email."""
|
||||
repo = UserRepository(User, db_session)
|
||||
|
||||
found_user = await repo.get_by_email(test_user.email)
|
||||
|
||||
assert found_user is not None
|
||||
assert found_user.id == test_user.id
|
||||
assert found_user.email == test_user.email
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_by_username(self, db_session: AsyncSession, test_user: User):
|
||||
"""Test get user by username."""
|
||||
repo = UserRepository(User, db_session)
|
||||
|
||||
found_user = await repo.get_by_username(test_user.username)
|
||||
|
||||
assert found_user is not None
|
||||
assert found_user.id == test_user.id
|
||||
assert found_user.username == test_user.username
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_multi_with_pagination(
|
||||
self,
|
||||
db_session: AsyncSession,
|
||||
test_user: User
|
||||
):
|
||||
"""Test get multiple users with pagination."""
|
||||
repo = UserRepository(User, db_session)
|
||||
|
||||
# Create additional users
|
||||
for i in range(5):
|
||||
user_data = {
|
||||
"username": f"user{i}",
|
||||
"email": f"user{i}@example.com",
|
||||
"hashed_password": get_password_hash("password123"),
|
||||
"first_name": f"User{i}",
|
||||
"last_name": "Test"
|
||||
}
|
||||
await repo.create(user_data)
|
||||
|
||||
# Test pagination
|
||||
users = await repo.get_multi(skip=0, limit=3)
|
||||
assert len(users) == 3
|
||||
|
||||
users_page_2 = await repo.get_multi(skip=3, limit=3)
|
||||
assert len(users_page_2) >= 1 # At least test_user
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_update_user(self, db_session: AsyncSession, test_user: User):
|
||||
"""Test user update."""
|
||||
repo = UserRepository(User, db_session)
|
||||
|
||||
updated_user = await repo.update(test_user.id, {
|
||||
"first_name": "Updated",
|
||||
"bio": "Updated bio"
|
||||
})
|
||||
|
||||
assert updated_user is not None
|
||||
assert updated_user.first_name == "Updated"
|
||||
assert updated_user.bio == "Updated bio"
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_delete_user(self, db_session: AsyncSession, test_user: User):
|
||||
"""Test user deletion."""
|
||||
repo = UserRepository(User, db_session)
|
||||
|
||||
result = await repo.delete(test_user.id)
|
||||
assert result is True
|
||||
|
||||
# Verify user is deleted
|
||||
deleted_user = await repo.get(test_user.id)
|
||||
assert deleted_user is None
|
||||
```
|
||||
|
||||
## Performance Testing
|
||||
|
||||
```python
|
||||
# tests/test_performance.py
|
||||
import pytest
|
||||
import time
|
||||
import asyncio
|
||||
from httpx import AsyncClient
|
||||
from app.models.user import User
|
||||
from tests.factories import UserFactory
|
||||
|
||||
@pytest.mark.slow
|
||||
class TestPerformance:
|
||||
"""Test application performance."""
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_concurrent_requests(
|
||||
self,
|
||||
async_client: AsyncClient,
|
||||
auth_headers: dict
|
||||
):
|
||||
"""Test concurrent API requests."""
|
||||
|
||||
async def make_request():
|
||||
response = await async_client.get(
|
||||
"/api/v1/users/",
|
||||
headers=auth_headers
|
||||
)
|
||||
return response.status_code
|
||||
|
||||
# Make 10 concurrent requests
|
||||
start_time = time.time()
|
||||
tasks = [make_request() for _ in range(10)]
|
||||
results = await asyncio.gather(*tasks)
|
||||
end_time = time.time()
|
||||
|
||||
# All requests should succeed
|
||||
assert all(status == 200 for status in results)
|
||||
|
||||
# Should complete within reasonable time
|
||||
assert (end_time - start_time) < 5.0
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_large_dataset_pagination(
|
||||
self,
|
||||
async_client: AsyncClient,
|
||||
db_session,
|
||||
auth_headers: dict
|
||||
):
|
||||
"""Test pagination with large dataset."""
|
||||
# Create 100 users
|
||||
users = []
|
||||
for i in range(100):
|
||||
user = UserFactory.build()
|
||||
users.append(user)
|
||||
|
||||
db_session.add_all(users)
|
||||
await db_session.commit()
|
||||
|
||||
# Test pagination performance
|
||||
start_time = time.time()
|
||||
response = await async_client.get(
|
||||
"/api/v1/users/?skip=0&limit=50",
|
||||
headers=auth_headers
|
||||
)
|
||||
end_time = time.time()
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert len(data["items"]) == 50
|
||||
|
||||
# Should complete quickly
|
||||
assert (end_time - start_time) < 1.0
|
||||
```
|
||||
|
||||
## Mocking External Services
|
||||
|
||||
```python
|
||||
# tests/test_external.py
|
||||
import pytest
|
||||
import respx
|
||||
import httpx
|
||||
from app.services.email import EmailService
|
||||
|
||||
class TestExternalServices:
|
||||
"""Test external service integrations."""
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@respx.mock
|
||||
async def test_email_service(
|
||||
self,
|
||||
async_client: AsyncClient
|
||||
):
|
||||
"""Test email service with mocked external API."""
|
||||
# Mock email service API
|
||||
respx.post("https://api.emailservice.com/send").mock(
|
||||
return_value=httpx.Response(
|
||||
200,
|
||||
json={"message": "Email sent successfully"}
|
||||
)
|
||||
)
|
||||
|
||||
email_service = EmailService()
|
||||
result = await email_service.send_email(
|
||||
to="test@example.com",
|
||||
subject="Test",
|
||||
body="Test email"
|
||||
)
|
||||
|
||||
assert result["success"] is True
|
||||
```
|
||||
|
||||
## Test Utilities
|
||||
|
||||
```python
|
||||
# tests/utils.py
|
||||
from typing import Dict, Any
|
||||
from httpx import Response
|
||||
import json
|
||||
|
||||
def assert_response_status(response: Response, expected_status: int = 200):
|
||||
"""Assert response status code."""
|
||||
assert response.status_code == expected_status, f"Expected {expected_status}, got {response.status_code}. Response: {response.text}"
|
||||
|
||||
def assert_response_json(response: Response, expected_keys: list[str] = None):
|
||||
"""Assert response is valid JSON with expected keys."""
|
||||
assert response.headers.get("content-type") == "application/json"
|
||||
data = response.json()
|
||||
|
||||
if expected_keys:
|
||||
for key in expected_keys:
|
||||
assert key in data, f"Missing key '{key}' in response"
|
||||
|
||||
return data
|
||||
|
||||
def create_auth_headers(token: str) -> Dict[str, str]:
|
||||
"""Create authorization headers with token."""
|
||||
return {"Authorization": f"Bearer {token}"}
|
||||
|
||||
async def create_test_users(db_session, count: int = 5) -> list:
|
||||
"""Create multiple test users."""
|
||||
from tests.factories import UserFactory
|
||||
|
||||
users = []
|
||||
for i in range(count):
|
||||
user = UserFactory.build()
|
||||
users.append(user)
|
||||
|
||||
db_session.add_all(users)
|
||||
await db_session.commit()
|
||||
|
||||
return users
|
||||
```
|
||||
@@ -0,0 +1,229 @@
|
||||
# FastAPI Project Configuration
|
||||
|
||||
This file provides specific guidance for FastAPI web application development using Claude Code.
|
||||
|
||||
## Project Overview
|
||||
|
||||
This is a FastAPI application project optimized for modern API development with automatic documentation, type hints, and async support.
|
||||
|
||||
## FastAPI-Specific Development Commands
|
||||
|
||||
### Project Management
|
||||
- `uvicorn app.main:app --reload` - Start development server with auto-reload
|
||||
- `uvicorn app.main:app --host 0.0.0.0 --port 8000` - Start server on all interfaces
|
||||
- `uvicorn app.main:app --workers 4` - Start with multiple workers
|
||||
|
||||
### Database Management
|
||||
- `alembic init alembic` - Initialize Alembic migrations
|
||||
- `alembic revision --autogenerate -m "message"` - Create migration
|
||||
- `alembic upgrade head` - Apply migrations
|
||||
- `alembic downgrade -1` - Rollback one migration
|
||||
|
||||
### Development Tools
|
||||
- `python -m pytest` - Run tests
|
||||
- `python -m pytest --cov=app` - Run tests with coverage
|
||||
- `mypy app/` - Type checking
|
||||
- `black app/` - Code formatting
|
||||
|
||||
## FastAPI Project Structure
|
||||
|
||||
```
|
||||
myproject/
|
||||
├── app/ # Application package
|
||||
│ ├── __init__.py
|
||||
│ ├── main.py # FastAPI application
|
||||
│ ├── core/ # Core configuration
|
||||
│ │ ├── __init__.py
|
||||
│ │ ├── config.py # Settings
|
||||
│ │ └── security.py # Authentication
|
||||
│ ├── api/ # API routes
|
||||
│ │ ├── __init__.py
|
||||
│ │ ├── deps.py # Dependencies
|
||||
│ │ └── v1/
|
||||
│ │ ├── __init__.py
|
||||
│ │ ├── api.py # API router
|
||||
│ │ └── endpoints/
|
||||
│ ├── models/ # Database models
|
||||
│ │ ├── __init__.py
|
||||
│ │ ├── base.py
|
||||
│ │ └── user.py
|
||||
│ ├── schemas/ # Pydantic schemas
|
||||
│ │ ├── __init__.py
|
||||
│ │ └── user.py
|
||||
│ ├── repositories/ # Data access layer
|
||||
│ │ ├── __init__.py
|
||||
│ │ └── user.py
|
||||
│ ├── services/ # Business logic
|
||||
│ │ ├── __init__.py
|
||||
│ │ └── auth.py
|
||||
│ └── db/ # Database configuration
|
||||
│ ├── __init__.py
|
||||
│ └── database.py
|
||||
├── alembic/ # Database migrations
|
||||
├── tests/ # Test files
|
||||
├── requirements.txt # Dependencies
|
||||
└── docker-compose.yml # Docker configuration
|
||||
```
|
||||
|
||||
## FastAPI Application Setup
|
||||
|
||||
```python
|
||||
# app/main.py
|
||||
from fastapi import FastAPI
|
||||
from app.core.config import settings
|
||||
from app.api.v1.api import api_router
|
||||
|
||||
app = FastAPI(
|
||||
title=settings.PROJECT_NAME,
|
||||
version=settings.VERSION,
|
||||
openapi_url=f"/api/v1/openapi.json"
|
||||
)
|
||||
|
||||
# Include routers
|
||||
app.include_router(api_router, prefix="/api/v1")
|
||||
|
||||
@app.get("/")
|
||||
async def root():
|
||||
return {"message": "Welcome to FastAPI"}
|
||||
```
|
||||
|
||||
## Configuration Management
|
||||
|
||||
```python
|
||||
# app/core/config.py
|
||||
from pydantic import BaseSettings
|
||||
|
||||
class Settings(BaseSettings):
|
||||
PROJECT_NAME: str = "FastAPI App"
|
||||
VERSION: str = "1.0.0"
|
||||
SECRET_KEY: str
|
||||
DATABASE_URL: str
|
||||
|
||||
class Config:
|
||||
env_file = ".env"
|
||||
|
||||
settings = Settings()
|
||||
```
|
||||
|
||||
## FastAPI Best Practices
|
||||
|
||||
### API Design
|
||||
- Use Pydantic models for request/response validation
|
||||
- Implement proper HTTP status codes
|
||||
- Add comprehensive API documentation
|
||||
- Use dependency injection for common functionality
|
||||
- Implement proper error handling
|
||||
|
||||
### Database Integration
|
||||
- Use SQLAlchemy with async support
|
||||
- Implement repository pattern for data access
|
||||
- Use Alembic for database migrations
|
||||
- Add proper database connection pooling
|
||||
- Implement database health checks
|
||||
|
||||
### Authentication & Security
|
||||
- Use JWT tokens for authentication
|
||||
- Implement OAuth2 with scopes
|
||||
- Add rate limiting for API endpoints
|
||||
- Use HTTPS in production
|
||||
- Implement proper CORS configuration
|
||||
|
||||
### Performance Optimization
|
||||
- Use async/await for I/O operations
|
||||
- Implement response caching
|
||||
- Add database query optimization
|
||||
- Use connection pooling
|
||||
- Monitor application performance
|
||||
|
||||
## Testing Strategy
|
||||
|
||||
### Test Organization
|
||||
```python
|
||||
# tests/conftest.py
|
||||
import pytest
|
||||
from fastapi.testclient import TestClient
|
||||
from app.main import app
|
||||
|
||||
@pytest.fixture
|
||||
def client():
|
||||
return TestClient(app)
|
||||
```
|
||||
|
||||
### Test Types
|
||||
- **Unit tests** for business logic
|
||||
- **Integration tests** for API endpoints
|
||||
- **Database tests** with test fixtures
|
||||
- **Authentication tests** for security
|
||||
|
||||
## Deployment Considerations
|
||||
|
||||
### Production Setup
|
||||
- Use Uvicorn with multiple workers
|
||||
- Implement proper logging and monitoring
|
||||
- Set up reverse proxy (Nginx)
|
||||
- Use environment variables for configuration
|
||||
- Implement health checks
|
||||
|
||||
### Docker Configuration
|
||||
```dockerfile
|
||||
FROM python:3.11-slim
|
||||
WORKDIR /app
|
||||
COPY requirements.txt .
|
||||
RUN pip install -r requirements.txt
|
||||
COPY . .
|
||||
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0"]
|
||||
```
|
||||
|
||||
### Environment Variables
|
||||
```bash
|
||||
SECRET_KEY=your-secret-key
|
||||
DATABASE_URL=postgresql://user:pass@host/db
|
||||
REDIS_URL=redis://localhost:6379
|
||||
```
|
||||
|
||||
## Common FastAPI Patterns
|
||||
|
||||
### Dependency Injection
|
||||
```python
|
||||
from fastapi import Depends
|
||||
from app.db.database import get_db
|
||||
|
||||
@app.get("/users/")
|
||||
async def get_users(db: Session = Depends(get_db)):
|
||||
return users
|
||||
```
|
||||
|
||||
### Background Tasks
|
||||
```python
|
||||
from fastapi import BackgroundTasks
|
||||
|
||||
@app.post("/send-email/")
|
||||
async def send_email(background_tasks: BackgroundTasks):
|
||||
background_tasks.add_task(send_email_task)
|
||||
return {"message": "Email sent"}
|
||||
```
|
||||
|
||||
### Middleware
|
||||
```python
|
||||
@app.middleware("http")
|
||||
async def add_process_time_header(request, call_next):
|
||||
response = await call_next(request)
|
||||
return response
|
||||
```
|
||||
|
||||
## Development Workflow
|
||||
|
||||
### Getting Started
|
||||
1. Clone repository
|
||||
2. Create virtual environment: `python -m venv venv`
|
||||
3. Install dependencies: `pip install -r requirements.txt`
|
||||
4. Set environment variables
|
||||
5. Run migrations: `alembic upgrade head`
|
||||
6. Start server: `uvicorn app.main:app --reload`
|
||||
|
||||
### Code Quality
|
||||
- **Black** - Code formatting
|
||||
- **isort** - Import sorting
|
||||
- **mypy** - Type checking
|
||||
- **pytest** - Testing framework
|
||||
- **flake8** - Linting
|
||||
@@ -0,0 +1,384 @@
|
||||
# Flask Application Factory Pattern
|
||||
|
||||
Create a scalable Flask application using the factory pattern with blueprints and configuration management.
|
||||
|
||||
## Purpose
|
||||
|
||||
This command helps you set up a Flask application using the application factory pattern, which is the recommended approach for larger Flask applications.
|
||||
|
||||
## Usage
|
||||
|
||||
```
|
||||
/app-factory
|
||||
```
|
||||
|
||||
## What this command does
|
||||
|
||||
1. **Creates application factory** with proper structure
|
||||
2. **Sets up configuration management** for different environments
|
||||
3. **Implements blueprints** for modular design
|
||||
4. **Configures extensions** (database, auth, etc.)
|
||||
5. **Adds error handling** and logging
|
||||
|
||||
## Example Output
|
||||
|
||||
```python
|
||||
# app/__init__.py
|
||||
from flask import Flask
|
||||
from flask_sqlalchemy import SQLAlchemy
|
||||
from flask_migrate import Migrate
|
||||
from flask_login import LoginManager
|
||||
from flask_mail import Mail
|
||||
from flask_wtf.csrf import CSRFProtect
|
||||
from flask_cors import CORS
|
||||
import logging
|
||||
from logging.handlers import RotatingFileHandler
|
||||
import os
|
||||
|
||||
# Initialize extensions
|
||||
db = SQLAlchemy()
|
||||
migrate = Migrate()
|
||||
login = LoginManager()
|
||||
mail = Mail()
|
||||
csrf = CSRFProtect()
|
||||
cors = CORS()
|
||||
|
||||
def create_app(config_class=None):
|
||||
"""Application factory function."""
|
||||
app = Flask(__name__)
|
||||
|
||||
# Load configuration
|
||||
if config_class is None:
|
||||
config_class = os.environ.get('FLASK_CONFIG', 'development')
|
||||
|
||||
if isinstance(config_class, str):
|
||||
from app.config import config
|
||||
app.config.from_object(config[config_class])
|
||||
else:
|
||||
app.config.from_object(config_class)
|
||||
|
||||
# Initialize extensions
|
||||
db.init_app(app)
|
||||
migrate.init_app(app, db)
|
||||
login.init_app(app)
|
||||
mail.init_app(app)
|
||||
csrf.init_app(app)
|
||||
cors.init_app(app)
|
||||
|
||||
# Configure login manager
|
||||
login.login_view = 'auth.login'
|
||||
login.login_message = 'Please log in to access this page.'
|
||||
login.login_message_category = 'info'
|
||||
|
||||
# Register blueprints
|
||||
from app.main import bp as main_bp
|
||||
app.register_blueprint(main_bp)
|
||||
|
||||
from app.auth import bp as auth_bp
|
||||
app.register_blueprint(auth_bp, url_prefix='/auth')
|
||||
|
||||
from app.api import bp as api_bp
|
||||
app.register_blueprint(api_bp, url_prefix='/api')
|
||||
|
||||
from app.admin import bp as admin_bp
|
||||
app.register_blueprint(admin_bp, url_prefix='/admin')
|
||||
|
||||
# Error handlers
|
||||
from app.errors import bp as errors_bp
|
||||
app.register_blueprint(errors_bp)
|
||||
|
||||
# Configure logging
|
||||
if not app.debug and not app.testing:
|
||||
if not os.path.exists('logs'):
|
||||
os.mkdir('logs')
|
||||
|
||||
file_handler = RotatingFileHandler(
|
||||
'logs/app.log',
|
||||
maxBytes=10240,
|
||||
backupCount=10
|
||||
)
|
||||
file_handler.setFormatter(logging.Formatter(
|
||||
'%(asctime)s %(levelname)s: %(message)s [in %(pathname)s:%(lineno)d]'
|
||||
))
|
||||
file_handler.setLevel(logging.INFO)
|
||||
app.logger.addHandler(file_handler)
|
||||
|
||||
app.logger.setLevel(logging.INFO)
|
||||
app.logger.info('Flask application startup')
|
||||
|
||||
return app
|
||||
|
||||
# Import models (avoid circular imports)
|
||||
from app import models
|
||||
```
|
||||
|
||||
```python
|
||||
# app/config.py
|
||||
import os
|
||||
from dotenv import load_dotenv
|
||||
|
||||
basedir = os.path.abspath(os.path.dirname(__file__))
|
||||
load_dotenv(os.path.join(basedir, '.env'))
|
||||
|
||||
class Config:
|
||||
"""Base configuration class."""
|
||||
SECRET_KEY = os.environ.get('SECRET_KEY') or 'dev-secret-key'
|
||||
SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL') or \
|
||||
'sqlite:///' + os.path.join(basedir, 'app.db')
|
||||
SQLALCHEMY_TRACK_MODIFICATIONS = False
|
||||
|
||||
# Mail configuration
|
||||
MAIL_SERVER = os.environ.get('MAIL_SERVER')
|
||||
MAIL_PORT = int(os.environ.get('MAIL_PORT') or 587)
|
||||
MAIL_USE_TLS = os.environ.get('MAIL_USE_TLS', 'true').lower() in ['true', 'on', '1']
|
||||
MAIL_USERNAME = os.environ.get('MAIL_USERNAME')
|
||||
MAIL_PASSWORD = os.environ.get('MAIL_PASSWORD')
|
||||
ADMIN_EMAIL = os.environ.get('ADMIN_EMAIL')
|
||||
|
||||
# Pagination
|
||||
POSTS_PER_PAGE = 10
|
||||
USERS_PER_PAGE = 50
|
||||
|
||||
# Upload configuration
|
||||
MAX_CONTENT_LENGTH = 16 * 1024 * 1024 # 16MB
|
||||
UPLOAD_FOLDER = os.path.join(basedir, 'uploads')
|
||||
|
||||
@staticmethod
|
||||
def init_app(app):
|
||||
pass
|
||||
|
||||
class DevelopmentConfig(Config):
|
||||
"""Development configuration."""
|
||||
DEBUG = True
|
||||
SQLALCHEMY_DATABASE_URI = os.environ.get('DEV_DATABASE_URL') or \
|
||||
'sqlite:///' + os.path.join(basedir, 'app-dev.db')
|
||||
|
||||
class TestingConfig(Config):
|
||||
"""Testing configuration."""
|
||||
TESTING = True
|
||||
SQLALCHEMY_DATABASE_URI = os.environ.get('TEST_DATABASE_URL') or 'sqlite://'
|
||||
WTF_CSRF_ENABLED = False
|
||||
|
||||
class ProductionConfig(Config):
|
||||
"""Production configuration."""
|
||||
SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL') or \
|
||||
'sqlite:///' + os.path.join(basedir, 'app.db')
|
||||
|
||||
@classmethod
|
||||
def init_app(cls, app):
|
||||
Config.init_app(app)
|
||||
|
||||
# Log to stderr
|
||||
import logging
|
||||
from logging import StreamHandler
|
||||
file_handler = StreamHandler()
|
||||
file_handler.setLevel(logging.INFO)
|
||||
app.logger.addHandler(file_handler)
|
||||
|
||||
config = {
|
||||
'development': DevelopmentConfig,
|
||||
'testing': TestingConfig,
|
||||
'production': ProductionConfig,
|
||||
'default': DevelopmentConfig
|
||||
}
|
||||
```
|
||||
|
||||
```python
|
||||
# app/main/__init__.py
|
||||
from flask import Blueprint
|
||||
|
||||
bp = Blueprint('main', __name__)
|
||||
|
||||
from app.main import routes
|
||||
```
|
||||
|
||||
```python
|
||||
# app/main/routes.py
|
||||
from flask import render_template, request, current_app
|
||||
from app.main import bp
|
||||
from app.models import Post
|
||||
|
||||
@bp.route('/')
|
||||
@bp.route('/index')
|
||||
def index():
|
||||
"""Home page."""
|
||||
page = request.args.get('page', 1, type=int)
|
||||
posts = Post.query.filter_by(published=True).order_by(
|
||||
Post.created_at.desc()
|
||||
).paginate(
|
||||
page=page,
|
||||
per_page=current_app.config['POSTS_PER_PAGE'],
|
||||
error_out=False
|
||||
)
|
||||
|
||||
return render_template('index.html', posts=posts)
|
||||
|
||||
@bp.route('/about')
|
||||
def about():
|
||||
"""About page."""
|
||||
return render_template('about.html')
|
||||
```
|
||||
|
||||
```python
|
||||
# app/auth/__init__.py
|
||||
from flask import Blueprint
|
||||
|
||||
bp = Blueprint('auth', __name__)
|
||||
|
||||
from app.auth import routes
|
||||
```
|
||||
|
||||
```python
|
||||
# app/auth/routes.py
|
||||
from flask import render_template, redirect, url_for, flash, request
|
||||
from flask_login import login_user, logout_user, login_required, current_user
|
||||
from werkzeug.urls import url_parse
|
||||
from app import db
|
||||
from app.auth import bp
|
||||
from app.auth.forms import LoginForm, RegistrationForm
|
||||
from app.models import User
|
||||
|
||||
@bp.route('/login', methods=['GET', 'POST'])
|
||||
def login():
|
||||
"""User login."""
|
||||
if current_user.is_authenticated:
|
||||
return redirect(url_for('main.index'))
|
||||
|
||||
form = LoginForm()
|
||||
if form.validate_on_submit():
|
||||
user = User.query.filter_by(username=form.username.data).first()
|
||||
if user is None or not user.check_password(form.password.data):
|
||||
flash('Invalid username or password', 'error')
|
||||
return redirect(url_for('auth.login'))
|
||||
|
||||
login_user(user, remember=form.remember_me.data)
|
||||
|
||||
next_page = request.args.get('next')
|
||||
if not next_page or url_parse(next_page).netloc != '':
|
||||
next_page = url_for('main.index')
|
||||
|
||||
return redirect(next_page)
|
||||
|
||||
return render_template('auth/login.html', form=form)
|
||||
|
||||
@bp.route('/logout')
|
||||
@login_required
|
||||
def logout():
|
||||
"""User logout."""
|
||||
logout_user()
|
||||
flash('You have been logged out.', 'info')
|
||||
return redirect(url_for('main.index'))
|
||||
|
||||
@bp.route('/register', methods=['GET', 'POST'])
|
||||
def register():
|
||||
"""User registration."""
|
||||
if current_user.is_authenticated:
|
||||
return redirect(url_for('main.index'))
|
||||
|
||||
form = RegistrationForm()
|
||||
if form.validate_on_submit():
|
||||
user = User(username=form.username.data, email=form.email.data)
|
||||
user.set_password(form.password.data)
|
||||
db.session.add(user)
|
||||
db.session.commit()
|
||||
|
||||
flash('Registration successful!', 'success')
|
||||
return redirect(url_for('auth.login'))
|
||||
|
||||
return render_template('auth/register.html', form=form)
|
||||
```
|
||||
|
||||
```python
|
||||
# app/models.py
|
||||
from app import db, login
|
||||
from werkzeug.security import generate_password_hash, check_password_hash
|
||||
from flask_login import UserMixin
|
||||
from datetime import datetime
|
||||
|
||||
class User(UserMixin, db.Model):
|
||||
"""User model."""
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
username = db.Column(db.String(64), index=True, unique=True)
|
||||
email = db.Column(db.String(120), index=True, unique=True)
|
||||
password_hash = db.Column(db.String(128))
|
||||
created_at = db.Column(db.DateTime, index=True, default=datetime.utcnow)
|
||||
|
||||
posts = db.relationship('Post', backref='author', lazy='dynamic')
|
||||
|
||||
def set_password(self, password):
|
||||
self.password_hash = generate_password_hash(password)
|
||||
|
||||
def check_password(self, password):
|
||||
return check_password_hash(self.password_hash, password)
|
||||
|
||||
def __repr__(self):
|
||||
return f'<User {self.username}>'
|
||||
|
||||
@login.user_loader
|
||||
def load_user(id):
|
||||
return User.query.get(int(id))
|
||||
|
||||
class Post(db.Model):
|
||||
"""Post model."""
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
title = db.Column(db.String(100), nullable=False)
|
||||
content = db.Column(db.Text, nullable=False)
|
||||
published = db.Column(db.Boolean, default=False)
|
||||
created_at = db.Column(db.DateTime, index=True, default=datetime.utcnow)
|
||||
updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
||||
|
||||
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
|
||||
|
||||
def __repr__(self):
|
||||
return f'<Post {self.title}>'
|
||||
```
|
||||
|
||||
```python
|
||||
# app.py (main application entry point)
|
||||
from app import create_app, db
|
||||
from app.models import User, Post
|
||||
|
||||
app = create_app()
|
||||
|
||||
@app.shell_context_processor
|
||||
def make_shell_context():
|
||||
"""Add models to shell context."""
|
||||
return {'db': db, 'User': User, 'Post': Post}
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run(debug=True)
|
||||
```
|
||||
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
project/
|
||||
├── app/
|
||||
│ ├── __init__.py # Application factory
|
||||
│ ├── config.py # Configuration classes
|
||||
│ ├── models.py # Database models
|
||||
│ ├── main/
|
||||
│ │ ├── __init__.py # Main blueprint
|
||||
│ │ └── routes.py # Main routes
|
||||
│ ├── auth/
|
||||
│ │ ├── __init__.py # Auth blueprint
|
||||
│ │ ├── routes.py # Auth routes
|
||||
│ │ └── forms.py # Auth forms
|
||||
│ ├── api/
|
||||
│ │ ├── __init__.py # API blueprint
|
||||
│ │ └── routes.py # API routes
|
||||
│ └── templates/ # Jinja2 templates
|
||||
├── migrations/ # Database migrations
|
||||
├── logs/ # Application logs
|
||||
├── app.py # Application entry point
|
||||
├── requirements.txt # Dependencies
|
||||
└── .env # Environment variables
|
||||
```
|
||||
|
||||
## Benefits of Application Factory
|
||||
|
||||
- **Testing**: Easy to create app instances with different configs
|
||||
- **Scalability**: Modular design with blueprints
|
||||
- **Configuration**: Environment-specific settings
|
||||
- **Extensions**: Proper initialization order
|
||||
- **Maintainability**: Clear separation of concerns
|
||||
@@ -0,0 +1,243 @@
|
||||
# Flask Blueprint Generator
|
||||
|
||||
Create organized Flask blueprints for modular application structure.
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
# Create a new blueprint
|
||||
flask create-blueprint users
|
||||
flask create-blueprint api/v1
|
||||
```
|
||||
|
||||
## Blueprint Structure
|
||||
|
||||
Generates a complete blueprint with:
|
||||
- Routes and view functions
|
||||
- Error handlers
|
||||
- Template folder structure
|
||||
- Static file organization
|
||||
|
||||
## Example Blueprint
|
||||
|
||||
```python
|
||||
# app/blueprints/users/__init__.py
|
||||
from flask import Blueprint
|
||||
|
||||
users_bp = Blueprint(
|
||||
'users',
|
||||
__name__,
|
||||
url_prefix='/users',
|
||||
template_folder='templates',
|
||||
static_folder='static'
|
||||
)
|
||||
|
||||
from . import routes, models
|
||||
|
||||
# app/blueprints/users/routes.py
|
||||
from flask import render_template, request, redirect, url_for, flash
|
||||
from . import users_bp
|
||||
from .models import User
|
||||
from .forms import UserForm
|
||||
|
||||
@users_bp.route('/')
|
||||
def index():
|
||||
"""List all users."""
|
||||
users = User.query.all()
|
||||
return render_template('users/index.html', users=users)
|
||||
|
||||
@users_bp.route('/create', methods=['GET', 'POST'])
|
||||
def create():
|
||||
"""Create a new user."""
|
||||
form = UserForm()
|
||||
if form.validate_on_submit():
|
||||
user = User(
|
||||
username=form.username.data,
|
||||
email=form.email.data
|
||||
)
|
||||
user.save()
|
||||
flash('User created successfully!', 'success')
|
||||
return redirect(url_for('users.index'))
|
||||
return render_template('users/create.html', form=form)
|
||||
|
||||
@users_bp.route('/<int:user_id>')
|
||||
def detail(user_id):
|
||||
"""Show user details."""
|
||||
user = User.query.get_or_404(user_id)
|
||||
return render_template('users/detail.html', user=user)
|
||||
|
||||
@users_bp.route('/<int:user_id>/edit', methods=['GET', 'POST'])
|
||||
def edit(user_id):
|
||||
"""Edit an existing user."""
|
||||
user = User.query.get_or_404(user_id)
|
||||
form = UserForm(obj=user)
|
||||
if form.validate_on_submit():
|
||||
user.username = form.username.data
|
||||
user.email = form.email.data
|
||||
user.save()
|
||||
flash('User updated successfully!', 'success')
|
||||
return redirect(url_for('users.detail', user_id=user.id))
|
||||
return render_template('users/edit.html', form=form, user=user)
|
||||
|
||||
@users_bp.route('/<int:user_id>/delete', methods=['POST'])
|
||||
def delete(user_id):
|
||||
"""Delete a user."""
|
||||
user = User.query.get_or_404(user_id)
|
||||
user.delete()
|
||||
flash('User deleted successfully!', 'success')
|
||||
return redirect(url_for('users.index'))
|
||||
|
||||
# Error handlers
|
||||
@users_bp.errorhandler(404)
|
||||
def not_found(error):
|
||||
return render_template('users/404.html'), 404
|
||||
|
||||
@users_bp.errorhandler(500)
|
||||
def internal_error(error):
|
||||
return render_template('users/500.html'), 500
|
||||
```
|
||||
|
||||
## Blueprint Models
|
||||
|
||||
```python
|
||||
# app/blueprints/users/models.py
|
||||
from app.extensions import db
|
||||
from werkzeug.security import generate_password_hash, check_password_hash
|
||||
from datetime import datetime
|
||||
|
||||
class User(db.Model):
|
||||
"""User model."""
|
||||
__tablename__ = 'users'
|
||||
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
username = db.Column(db.String(80), unique=True, nullable=False)
|
||||
email = db.Column(db.String(120), unique=True, nullable=False)
|
||||
password_hash = db.Column(db.String(255), nullable=False)
|
||||
created_at = db.Column(db.DateTime, default=datetime.utcnow)
|
||||
updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
||||
is_active = db.Column(db.Boolean, default=True)
|
||||
|
||||
def __repr__(self):
|
||||
return f'<User {self.username}>'
|
||||
|
||||
def set_password(self, password):
|
||||
"""Set password hash."""
|
||||
self.password_hash = generate_password_hash(password)
|
||||
|
||||
def check_password(self, password):
|
||||
"""Check password hash."""
|
||||
return check_password_hash(self.password_hash, password)
|
||||
|
||||
def save(self):
|
||||
"""Save user to database."""
|
||||
db.session.add(self)
|
||||
db.session.commit()
|
||||
|
||||
def delete(self):
|
||||
"""Delete user from database."""
|
||||
db.session.delete(self)
|
||||
db.session.commit()
|
||||
|
||||
def to_dict(self):
|
||||
"""Convert to dictionary."""
|
||||
return {
|
||||
'id': self.id,
|
||||
'username': self.username,
|
||||
'email': self.email,
|
||||
'created_at': self.created_at.isoformat(),
|
||||
'is_active': self.is_active
|
||||
}
|
||||
```
|
||||
|
||||
## Blueprint Forms
|
||||
|
||||
```python
|
||||
# app/blueprints/users/forms.py
|
||||
from flask_wtf import FlaskForm
|
||||
from wtforms import StringField, EmailField, PasswordField, BooleanField
|
||||
from wtforms.validators import DataRequired, Email, Length, EqualTo
|
||||
from .models import User
|
||||
|
||||
class UserForm(FlaskForm):
|
||||
"""User creation/edit form."""
|
||||
username = StringField(
|
||||
'Username',
|
||||
validators=[
|
||||
DataRequired(),
|
||||
Length(min=3, max=80)
|
||||
]
|
||||
)
|
||||
email = EmailField(
|
||||
'Email',
|
||||
validators=[
|
||||
DataRequired(),
|
||||
Email(),
|
||||
Length(max=120)
|
||||
]
|
||||
)
|
||||
password = PasswordField(
|
||||
'Password',
|
||||
validators=[
|
||||
DataRequired(),
|
||||
Length(min=8)
|
||||
]
|
||||
)
|
||||
confirm_password = PasswordField(
|
||||
'Confirm Password',
|
||||
validators=[
|
||||
DataRequired(),
|
||||
EqualTo('password', message='Passwords must match')
|
||||
]
|
||||
)
|
||||
is_active = BooleanField('Active')
|
||||
|
||||
def validate_username(self, field):
|
||||
"""Validate username uniqueness."""
|
||||
if User.query.filter_by(username=field.data).first():
|
||||
raise ValidationError('Username already exists.')
|
||||
|
||||
def validate_email(self, field):
|
||||
"""Validate email uniqueness."""
|
||||
if User.query.filter_by(email=field.data).first():
|
||||
raise ValidationError('Email already registered.')
|
||||
```
|
||||
|
||||
## Registration in Main App
|
||||
|
||||
```python
|
||||
# app/__init__.py
|
||||
from flask import Flask
|
||||
from app.blueprints.users import users_bp
|
||||
|
||||
def create_app():
|
||||
app = Flask(__name__)
|
||||
|
||||
# Register blueprints
|
||||
app.register_blueprint(users_bp)
|
||||
|
||||
return app
|
||||
```
|
||||
|
||||
## Template Structure
|
||||
|
||||
```
|
||||
templates/
|
||||
├── base.html
|
||||
└── users/
|
||||
├── index.html
|
||||
├── create.html
|
||||
├── detail.html
|
||||
├── edit.html
|
||||
├── 404.html
|
||||
└── 500.html
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
- Use blueprints to organize related functionality
|
||||
- Keep models, forms, and routes in separate files
|
||||
- Implement proper error handling
|
||||
- Use URL prefixes for namespacing
|
||||
- Follow RESTful routing conventions
|
||||
- Include comprehensive docstrings
|
||||
- Add form validation and CSRF protection
|
||||
@@ -0,0 +1,410 @@
|
||||
# Flask Database Management
|
||||
|
||||
Complete database setup and management for Flask applications using SQLAlchemy.
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
# Initialize database
|
||||
flask db init
|
||||
|
||||
# Create migration
|
||||
flask db migrate -m "Initial migration"
|
||||
|
||||
# Apply migrations
|
||||
flask db upgrade
|
||||
|
||||
# Downgrade migration
|
||||
flask db downgrade
|
||||
```
|
||||
|
||||
## Database Configuration
|
||||
|
||||
```python
|
||||
# config.py
|
||||
import os
|
||||
from urllib.parse import quote_plus
|
||||
|
||||
class Config:
|
||||
"""Base configuration."""
|
||||
SECRET_KEY = os.environ.get('SECRET_KEY') or 'dev-secret-key'
|
||||
SQLALCHEMY_TRACK_MODIFICATIONS = False
|
||||
SQLALCHEMY_RECORD_QUERIES = True
|
||||
|
||||
class DevelopmentConfig(Config):
|
||||
"""Development configuration."""
|
||||
DEBUG = True
|
||||
SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL') or \
|
||||
'sqlite:///app.db'
|
||||
|
||||
class ProductionConfig(Config):
|
||||
"""Production configuration."""
|
||||
DEBUG = False
|
||||
SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL') or \
|
||||
f"postgresql://{os.environ.get('DB_USER')}:{quote_plus(os.environ.get('DB_PASSWORD'))}@" \
|
||||
f"{os.environ.get('DB_HOST')}:{os.environ.get('DB_PORT', '5432')}/{os.environ.get('DB_NAME')}"
|
||||
|
||||
class TestingConfig(Config):
|
||||
"""Testing configuration."""
|
||||
TESTING = True
|
||||
SQLALCHEMY_DATABASE_URI = 'sqlite:///:memory:'
|
||||
WTF_CSRF_ENABLED = False
|
||||
|
||||
config = {
|
||||
'development': DevelopmentConfig,
|
||||
'production': ProductionConfig,
|
||||
'testing': TestingConfig,
|
||||
'default': DevelopmentConfig
|
||||
}
|
||||
```
|
||||
|
||||
## Database Extensions
|
||||
|
||||
```python
|
||||
# app/extensions.py
|
||||
from flask_sqlalchemy import SQLAlchemy
|
||||
from flask_migrate import Migrate
|
||||
from flask_login import LoginManager
|
||||
from flask_wtf.csrf import CSRFProtect
|
||||
from flask_caching import Cache
|
||||
from flask_limiter import Limiter
|
||||
from flask_limiter.util import get_remote_address
|
||||
|
||||
# Initialize extensions
|
||||
db = SQLAlchemy()
|
||||
migrate = Migrate()
|
||||
login_manager = LoginManager()
|
||||
csrf = CSRFProtect()
|
||||
cache = Cache()
|
||||
limiter = Limiter(key_func=get_remote_address)
|
||||
|
||||
def init_extensions(app):
|
||||
"""Initialize Flask extensions."""
|
||||
db.init_app(app)
|
||||
migrate.init_app(app, db)
|
||||
login_manager.init_app(app)
|
||||
csrf.init_app(app)
|
||||
cache.init_app(app)
|
||||
limiter.init_app(app)
|
||||
|
||||
# Configure login manager
|
||||
login_manager.login_view = 'auth.login'
|
||||
login_manager.login_message = 'Please log in to access this page.'
|
||||
login_manager.login_message_category = 'info'
|
||||
```
|
||||
|
||||
## Base Model
|
||||
|
||||
```python
|
||||
# app/models/base.py
|
||||
from app.extensions import db
|
||||
from datetime import datetime
|
||||
from sqlalchemy.ext.declarative import declared_attr
|
||||
|
||||
class TimestampMixin:
|
||||
"""Add timestamp fields to model."""
|
||||
created_at = db.Column(db.DateTime, default=datetime.utcnow, nullable=False)
|
||||
updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, nullable=False)
|
||||
|
||||
class BaseModel(db.Model, TimestampMixin):
|
||||
"""Base model with common functionality."""
|
||||
__abstract__ = True
|
||||
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
|
||||
@declared_attr
|
||||
def __tablename__(cls):
|
||||
return cls.__name__.lower()
|
||||
|
||||
def save(self, commit=True):
|
||||
"""Save model to database."""
|
||||
db.session.add(self)
|
||||
if commit:
|
||||
db.session.commit()
|
||||
return self
|
||||
|
||||
def delete(self, commit=True):
|
||||
"""Delete model from database."""
|
||||
db.session.delete(self)
|
||||
if commit:
|
||||
db.session.commit()
|
||||
|
||||
def update(self, **kwargs):
|
||||
"""Update model attributes."""
|
||||
for key, value in kwargs.items():
|
||||
if hasattr(self, key):
|
||||
setattr(self, key, value)
|
||||
return self.save()
|
||||
|
||||
def to_dict(self, exclude=None):
|
||||
"""Convert model to dictionary."""
|
||||
exclude = exclude or []
|
||||
return {
|
||||
column.name: getattr(self, column.name)
|
||||
for column in self.__table__.columns
|
||||
if column.name not in exclude
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def get_or_404(cls, id):
|
||||
"""Get model by ID or raise 404."""
|
||||
return cls.query.get_or_404(id)
|
||||
|
||||
@classmethod
|
||||
def create(cls, **kwargs):
|
||||
"""Create new model instance."""
|
||||
instance = cls(**kwargs)
|
||||
return instance.save()
|
||||
```
|
||||
|
||||
## Example Models
|
||||
|
||||
```python
|
||||
# app/models/user.py
|
||||
from app.extensions import db
|
||||
from app.models.base import BaseModel
|
||||
from werkzeug.security import generate_password_hash, check_password_hash
|
||||
from flask_login import UserMixin
|
||||
|
||||
class User(UserMixin, BaseModel):
|
||||
"""User model."""
|
||||
__tablename__ = 'users'
|
||||
|
||||
username = db.Column(db.String(80), unique=True, nullable=False, index=True)
|
||||
email = db.Column(db.String(120), unique=True, nullable=False, index=True)
|
||||
password_hash = db.Column(db.String(255), nullable=False)
|
||||
first_name = db.Column(db.String(50), nullable=False)
|
||||
last_name = db.Column(db.String(50), nullable=False)
|
||||
is_active = db.Column(db.Boolean, default=True, nullable=False)
|
||||
is_admin = db.Column(db.Boolean, default=False, nullable=False)
|
||||
last_login = db.Column(db.DateTime)
|
||||
|
||||
# Relationships
|
||||
posts = db.relationship('Post', backref='author', lazy='dynamic', cascade='all, delete-orphan')
|
||||
|
||||
def __repr__(self):
|
||||
return f'<User {self.username}>'
|
||||
|
||||
def set_password(self, password):
|
||||
"""Set password hash."""
|
||||
self.password_hash = generate_password_hash(password)
|
||||
|
||||
def check_password(self, password):
|
||||
"""Check password hash."""
|
||||
return check_password_hash(self.password_hash, password)
|
||||
|
||||
@property
|
||||
def full_name(self):
|
||||
"""Get user's full name."""
|
||||
return f"{self.first_name} {self.last_name}"
|
||||
|
||||
def to_dict(self, exclude=None):
|
||||
"""Convert to dictionary excluding sensitive data."""
|
||||
exclude = exclude or ['password_hash']
|
||||
return super().to_dict(exclude=exclude)
|
||||
|
||||
# app/models/post.py
|
||||
class Post(BaseModel):
|
||||
"""Blog post model."""
|
||||
__tablename__ = 'posts'
|
||||
|
||||
title = db.Column(db.String(200), nullable=False)
|
||||
content = db.Column(db.Text, nullable=False)
|
||||
slug = db.Column(db.String(200), unique=True, nullable=False, index=True)
|
||||
status = db.Column(db.String(20), default='draft', nullable=False)
|
||||
published_at = db.Column(db.DateTime)
|
||||
|
||||
# Foreign keys
|
||||
user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)
|
||||
category_id = db.Column(db.Integer, db.ForeignKey('categories.id'))
|
||||
|
||||
# Relationships
|
||||
category = db.relationship('Category', backref='posts')
|
||||
tags = db.relationship('Tag', secondary='post_tags', backref='posts')
|
||||
|
||||
def __repr__(self):
|
||||
return f'<Post {self.title}>'
|
||||
|
||||
@property
|
||||
def is_published(self):
|
||||
"""Check if post is published."""
|
||||
return self.status == 'published' and self.published_at is not None
|
||||
```
|
||||
|
||||
## Database CLI Commands
|
||||
|
||||
```python
|
||||
# app/cli.py
|
||||
import click
|
||||
from flask import current_app
|
||||
from flask.cli import with_appcontext
|
||||
from app.extensions import db
|
||||
from app.models import User, Post, Category
|
||||
|
||||
@click.command()
|
||||
@with_appcontext
|
||||
def init_db():
|
||||
"""Initialize database."""
|
||||
db.create_all()
|
||||
click.echo('Database initialized.')
|
||||
|
||||
@click.command()
|
||||
@with_appcontext
|
||||
def seed_db():
|
||||
"""Seed database with sample data."""
|
||||
# Create admin user
|
||||
admin = User(
|
||||
username='admin',
|
||||
email='admin@example.com',
|
||||
first_name='Admin',
|
||||
last_name='User',
|
||||
is_admin=True
|
||||
)
|
||||
admin.set_password('admin123')
|
||||
admin.save()
|
||||
|
||||
# Create sample category
|
||||
category = Category(
|
||||
name='Technology',
|
||||
description='Tech-related posts'
|
||||
)
|
||||
category.save()
|
||||
|
||||
# Create sample post
|
||||
post = Post(
|
||||
title='Welcome to Flask',
|
||||
content='This is a sample blog post.',
|
||||
slug='welcome-to-flask',
|
||||
status='published',
|
||||
user_id=admin.id,
|
||||
category_id=category.id
|
||||
)
|
||||
post.save()
|
||||
|
||||
click.echo('Database seeded with sample data.')
|
||||
|
||||
@click.command()
|
||||
@with_appcontext
|
||||
def reset_db():
|
||||
"""Reset database."""
|
||||
if click.confirm('Are you sure you want to reset the database?'):
|
||||
db.drop_all()
|
||||
db.create_all()
|
||||
click.echo('Database reset.')
|
||||
|
||||
def init_commands(app):
|
||||
"""Register CLI commands."""
|
||||
app.cli.add_command(init_db)
|
||||
app.cli.add_command(seed_db)
|
||||
app.cli.add_command(reset_db)
|
||||
```
|
||||
|
||||
## Connection Pooling
|
||||
|
||||
```python
|
||||
# app/database.py
|
||||
from sqlalchemy import create_engine
|
||||
from sqlalchemy.pool import QueuePool
|
||||
|
||||
def configure_database(app):
|
||||
"""Configure database with connection pooling."""
|
||||
if app.config.get('SQLALCHEMY_DATABASE_URI', '').startswith('postgresql'):
|
||||
# PostgreSQL configuration
|
||||
engine = create_engine(
|
||||
app.config['SQLALCHEMY_DATABASE_URI'],
|
||||
poolclass=QueuePool,
|
||||
pool_size=10,
|
||||
max_overflow=20,
|
||||
pool_recycle=3600,
|
||||
pool_pre_ping=True
|
||||
)
|
||||
app.config['SQLALCHEMY_ENGINE_OPTIONS'] = {
|
||||
'pool_size': 10,
|
||||
'max_overflow': 20,
|
||||
'pool_recycle': 3600,
|
||||
'pool_pre_ping': True
|
||||
}
|
||||
```
|
||||
|
||||
## Database Utilities
|
||||
|
||||
```python
|
||||
# app/utils/database.py
|
||||
from app.extensions import db
|
||||
from sqlalchemy import text
|
||||
from flask import current_app
|
||||
|
||||
def execute_sql(sql, params=None):
|
||||
"""Execute raw SQL query."""
|
||||
with db.engine.connect() as conn:
|
||||
result = conn.execute(text(sql), params or {})
|
||||
return result.fetchall()
|
||||
|
||||
def backup_database():
|
||||
"""Create database backup."""
|
||||
# Implementation depends on database type
|
||||
pass
|
||||
|
||||
def check_database_health():
|
||||
"""Check database connection health."""
|
||||
try:
|
||||
db.session.execute(text('SELECT 1'))
|
||||
return True
|
||||
except Exception as e:
|
||||
current_app.logger.error(f'Database health check failed: {e}')
|
||||
return False
|
||||
|
||||
def get_table_info(table_name):
|
||||
"""Get table information."""
|
||||
inspector = db.inspect(db.engine)
|
||||
return {
|
||||
'columns': inspector.get_columns(table_name),
|
||||
'indexes': inspector.get_indexes(table_name),
|
||||
'foreign_keys': inspector.get_foreign_keys(table_name)
|
||||
}
|
||||
```
|
||||
|
||||
## Testing Database
|
||||
|
||||
```python
|
||||
# tests/conftest.py
|
||||
import pytest
|
||||
from app import create_app
|
||||
from app.extensions import db
|
||||
from app.models import User
|
||||
|
||||
@pytest.fixture(scope='session')
|
||||
def app():
|
||||
"""Create test app."""
|
||||
app = create_app('testing')
|
||||
with app.app_context():
|
||||
db.create_all()
|
||||
yield app
|
||||
db.drop_all()
|
||||
|
||||
@pytest.fixture
|
||||
def client(app):
|
||||
"""Create test client."""
|
||||
return app.test_client()
|
||||
|
||||
@pytest.fixture
|
||||
def db_session(app):
|
||||
"""Create database session for testing."""
|
||||
with app.app_context():
|
||||
db.session.begin()
|
||||
yield db.session
|
||||
db.session.rollback()
|
||||
|
||||
@pytest.fixture
|
||||
def user(db_session):
|
||||
"""Create test user."""
|
||||
user = User(
|
||||
username='testuser',
|
||||
email='test@example.com',
|
||||
first_name='Test',
|
||||
last_name='User'
|
||||
)
|
||||
user.set_password('testpass')
|
||||
user.save()
|
||||
return user
|
||||
```
|
||||
@@ -0,0 +1,620 @@
|
||||
# Flask Deployment Configuration
|
||||
|
||||
Complete production deployment setup for Flask applications.
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
# Build Docker image
|
||||
docker build -t myapp .
|
||||
|
||||
# Run with Docker Compose
|
||||
docker-compose up -d
|
||||
|
||||
# Deploy to cloud
|
||||
gunicorn --bind 0.0.0.0:8000 app:app
|
||||
```
|
||||
|
||||
## Production Configuration
|
||||
|
||||
```python
|
||||
# config.py
|
||||
import os
|
||||
from urllib.parse import quote_plus
|
||||
|
||||
class ProductionConfig:
|
||||
"""Production configuration."""
|
||||
|
||||
# Security
|
||||
SECRET_KEY = os.environ.get('SECRET_KEY')
|
||||
DEBUG = False
|
||||
TESTING = False
|
||||
|
||||
# Database
|
||||
SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL') or \
|
||||
f"postgresql://{os.environ.get('DB_USER')}:{quote_plus(os.environ.get('DB_PASSWORD'))}@" \
|
||||
f"{os.environ.get('DB_HOST')}:{os.environ.get('DB_PORT', '5432')}/{os.environ.get('DB_NAME')}"
|
||||
SQLALCHEMY_TRACK_MODIFICATIONS = False
|
||||
SQLALCHEMY_ENGINE_OPTIONS = {
|
||||
'pool_size': 10,
|
||||
'max_overflow': 20,
|
||||
'pool_recycle': 3600,
|
||||
'pool_pre_ping': True
|
||||
}
|
||||
|
||||
# Security Headers
|
||||
SECURITY_HEADERS = {
|
||||
'Strict-Transport-Security': 'max-age=31536000; includeSubDomains',
|
||||
'X-Content-Type-Options': 'nosniff',
|
||||
'X-Frame-Options': 'DENY',
|
||||
'X-XSS-Protection': '1; mode=block',
|
||||
'Content-Security-Policy': "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'"
|
||||
}
|
||||
|
||||
# Session
|
||||
SESSION_COOKIE_SECURE = True
|
||||
SESSION_COOKIE_HTTPONLY = True
|
||||
SESSION_COOKIE_SAMESITE = 'Lax'
|
||||
PERMANENT_SESSION_LIFETIME = 3600 # 1 hour
|
||||
|
||||
# Cache
|
||||
CACHE_TYPE = 'redis'
|
||||
CACHE_REDIS_URL = os.environ.get('REDIS_URL', 'redis://localhost:6379/0')
|
||||
CACHE_DEFAULT_TIMEOUT = 300
|
||||
|
||||
# Rate Limiting
|
||||
RATELIMIT_STORAGE_URL = os.environ.get('REDIS_URL', 'redis://localhost:6379/1')
|
||||
RATELIMIT_DEFAULT = '100/hour'
|
||||
|
||||
# Logging
|
||||
LOG_LEVEL = os.environ.get('LOG_LEVEL', 'INFO')
|
||||
LOG_FILE = os.environ.get('LOG_FILE', '/var/log/app/app.log')
|
||||
|
||||
# File Upload
|
||||
MAX_CONTENT_LENGTH = 16 * 1024 * 1024 # 16MB
|
||||
UPLOAD_FOLDER = os.environ.get('UPLOAD_FOLDER', '/var/uploads')
|
||||
|
||||
# Email
|
||||
MAIL_SERVER = os.environ.get('MAIL_SERVER')
|
||||
MAIL_PORT = int(os.environ.get('MAIL_PORT', 587))
|
||||
MAIL_USE_TLS = os.environ.get('MAIL_USE_TLS', 'true').lower() in ['true', 'on', '1']
|
||||
MAIL_USERNAME = os.environ.get('MAIL_USERNAME')
|
||||
MAIL_PASSWORD = os.environ.get('MAIL_PASSWORD')
|
||||
MAIL_DEFAULT_SENDER = os.environ.get('MAIL_DEFAULT_SENDER')
|
||||
```
|
||||
|
||||
## WSGI Configuration
|
||||
|
||||
```python
|
||||
# wsgi.py
|
||||
import os
|
||||
from app import create_app
|
||||
|
||||
# Get environment
|
||||
config_name = os.environ.get('FLASK_ENV', 'production')
|
||||
app = create_app(config_name)
|
||||
|
||||
if __name__ == "__main__":
|
||||
app.run()
|
||||
```
|
||||
|
||||
## Gunicorn Configuration
|
||||
|
||||
```python
|
||||
# gunicorn.conf.py
|
||||
import multiprocessing
|
||||
import os
|
||||
|
||||
# Server socket
|
||||
bind = f"0.0.0.0:{os.environ.get('PORT', 8000)}"
|
||||
backlog = 2048
|
||||
|
||||
# Worker processes
|
||||
workers = multiprocessing.cpu_count() * 2 + 1
|
||||
worker_class = 'sync'
|
||||
worker_connections = 1000
|
||||
timeout = 30
|
||||
keepalive = 60
|
||||
max_requests = 1000
|
||||
max_requests_jitter = 100
|
||||
|
||||
# Security
|
||||
limit_request_line = 4094
|
||||
limit_request_fields = 100
|
||||
limit_request_field_size = 8190
|
||||
|
||||
# Logging
|
||||
accesslog = '-'
|
||||
errorlog = '-'
|
||||
loglevel = os.environ.get('LOG_LEVEL', 'info').lower()
|
||||
access_log_format = '%(h)s %(l)s %(u)s %(t)s "%(r)s" %(s)s %(b)s "%(f)s" "%(a)s" %(D)s'
|
||||
|
||||
# Process naming
|
||||
proc_name = 'flask_app'
|
||||
|
||||
# Server mechanics
|
||||
daemon = False
|
||||
pidfile = '/tmp/gunicorn.pid'
|
||||
user = os.environ.get('USER', 'www-data')
|
||||
group = os.environ.get('GROUP', 'www-data')
|
||||
tmp_upload_dir = None
|
||||
|
||||
# SSL
|
||||
keyfile = os.environ.get('SSL_KEYFILE')
|
||||
certfile = os.environ.get('SSL_CERTFILE')
|
||||
```
|
||||
|
||||
## Docker Configuration
|
||||
|
||||
```dockerfile
|
||||
# Dockerfile
|
||||
FROM python:3.11-slim
|
||||
|
||||
# Set environment variables
|
||||
ENV PYTHONDONTWRITEBYTECODE=1 \
|
||||
PYTHONUNBUFFERED=1 \
|
||||
PIP_NO_CACHE_DIR=1 \
|
||||
PIP_DISABLE_PIP_VERSION_CHECK=1
|
||||
|
||||
# Install system dependencies
|
||||
RUN apt-get update && apt-get install -y \
|
||||
build-essential \
|
||||
libpq-dev \
|
||||
curl \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Create app user
|
||||
RUN groupadd -r appuser && useradd -r -g appuser appuser
|
||||
|
||||
# Set work directory
|
||||
WORKDIR /app
|
||||
|
||||
# Install Python dependencies
|
||||
COPY requirements/production.txt ./requirements.txt
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
|
||||
# Copy application code
|
||||
COPY . .
|
||||
|
||||
# Create necessary directories
|
||||
RUN mkdir -p /var/log/app /var/uploads && \
|
||||
chown -R appuser:appuser /app /var/log/app /var/uploads
|
||||
|
||||
# Switch to non-root user
|
||||
USER appuser
|
||||
|
||||
# Expose port
|
||||
EXPOSE 8000
|
||||
|
||||
# Health check
|
||||
HEALTHCHECK --interval=30s --timeout=30s --start-period=5s --retries=3 \
|
||||
CMD curl -f http://localhost:8000/health || exit 1
|
||||
|
||||
# Run application
|
||||
CMD ["gunicorn", "--config", "gunicorn.conf.py", "wsgi:app"]
|
||||
```
|
||||
|
||||
## Docker Compose
|
||||
|
||||
```yaml
|
||||
# docker-compose.yml
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
web:
|
||||
build: .
|
||||
ports:
|
||||
- "8000:8000"
|
||||
environment:
|
||||
- FLASK_ENV=production
|
||||
- DATABASE_URL=postgresql://postgres:password@db:5432/myapp
|
||||
- REDIS_URL=redis://redis:6379/0
|
||||
- SECRET_KEY=${SECRET_KEY}
|
||||
depends_on:
|
||||
- db
|
||||
- redis
|
||||
volumes:
|
||||
- uploads:/var/uploads
|
||||
- logs:/var/log/app
|
||||
restart: unless-stopped
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
|
||||
db:
|
||||
image: postgres:15
|
||||
environment:
|
||||
- POSTGRES_DB=myapp
|
||||
- POSTGRES_USER=postgres
|
||||
- POSTGRES_PASSWORD=password
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql/data
|
||||
restart: unless-stopped
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U postgres"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
|
||||
redis:
|
||||
image: redis:7-alpine
|
||||
command: redis-server --appendonly yes
|
||||
volumes:
|
||||
- redis_data:/data
|
||||
restart: unless-stopped
|
||||
healthcheck:
|
||||
test: ["CMD", "redis-cli", "ping"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
|
||||
nginx:
|
||||
image: nginx:alpine
|
||||
ports:
|
||||
- "80:80"
|
||||
- "443:443"
|
||||
volumes:
|
||||
- ./nginx.conf:/etc/nginx/nginx.conf:ro
|
||||
- ./ssl:/etc/nginx/ssl:ro
|
||||
- uploads:/var/uploads:ro
|
||||
depends_on:
|
||||
- web
|
||||
restart: unless-stopped
|
||||
|
||||
volumes:
|
||||
postgres_data:
|
||||
redis_data:
|
||||
uploads:
|
||||
logs:
|
||||
```
|
||||
|
||||
## Nginx Configuration
|
||||
|
||||
```nginx
|
||||
# nginx.conf
|
||||
events {
|
||||
worker_connections 1024;
|
||||
}
|
||||
|
||||
http {
|
||||
upstream app {
|
||||
server web:8000;
|
||||
}
|
||||
|
||||
# Rate limiting
|
||||
limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
|
||||
limit_req_zone $binary_remote_addr zone=login:10m rate=1r/s;
|
||||
|
||||
# SSL configuration
|
||||
ssl_protocols TLSv1.2 TLSv1.3;
|
||||
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384;
|
||||
ssl_prefer_server_ciphers off;
|
||||
|
||||
# Security headers
|
||||
add_header X-Frame-Options DENY;
|
||||
add_header X-Content-Type-Options nosniff;
|
||||
add_header X-XSS-Protection "1; mode=block";
|
||||
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
server_name example.com www.example.com;
|
||||
return 301 https://$server_name$request_uri;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 443 ssl http2;
|
||||
server_name example.com www.example.com;
|
||||
|
||||
ssl_certificate /etc/nginx/ssl/cert.pem;
|
||||
ssl_certificate_key /etc/nginx/ssl/key.pem;
|
||||
|
||||
# File upload size
|
||||
client_max_body_size 16M;
|
||||
|
||||
# Static files
|
||||
location /static/ {
|
||||
alias /var/uploads/static/;
|
||||
expires 1y;
|
||||
add_header Cache-Control "public, immutable";
|
||||
}
|
||||
|
||||
location /uploads/ {
|
||||
alias /var/uploads/;
|
||||
expires 1h;
|
||||
}
|
||||
|
||||
# API rate limiting
|
||||
location /api/ {
|
||||
limit_req zone=api burst=20 nodelay;
|
||||
proxy_pass http://app;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
|
||||
# Login rate limiting
|
||||
location /auth/login {
|
||||
limit_req zone=login burst=5 nodelay;
|
||||
proxy_pass http://app;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
|
||||
# Main application
|
||||
location / {
|
||||
proxy_pass http://app;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
|
||||
# Timeout settings
|
||||
proxy_connect_timeout 60s;
|
||||
proxy_send_timeout 60s;
|
||||
proxy_read_timeout 60s;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Environment Variables
|
||||
|
||||
```bash
|
||||
# .env.production
|
||||
# Application
|
||||
FLASK_ENV=production
|
||||
SECRET_KEY=your-super-secret-key-here
|
||||
|
||||
# Database
|
||||
DATABASE_URL=postgresql://user:password@localhost:5432/myapp
|
||||
DB_HOST=localhost
|
||||
DB_PORT=5432
|
||||
DB_NAME=myapp
|
||||
DB_USER=user
|
||||
DB_PASSWORD=password
|
||||
|
||||
# Redis
|
||||
REDIS_URL=redis://localhost:6379/0
|
||||
|
||||
# Email
|
||||
MAIL_SERVER=smtp.gmail.com
|
||||
MAIL_PORT=587
|
||||
MAIL_USE_TLS=true
|
||||
MAIL_USERNAME=your-email@gmail.com
|
||||
MAIL_PASSWORD=your-app-password
|
||||
MAIL_DEFAULT_SENDER=your-email@gmail.com
|
||||
|
||||
# Logging
|
||||
LOG_LEVEL=INFO
|
||||
LOG_FILE=/var/log/app/app.log
|
||||
|
||||
# File Upload
|
||||
UPLOAD_FOLDER=/var/uploads
|
||||
|
||||
# SSL (if using)
|
||||
SSL_KEYFILE=/path/to/private.key
|
||||
SSL_CERTFILE=/path/to/certificate.crt
|
||||
```
|
||||
|
||||
## Health Check Endpoint
|
||||
|
||||
```python
|
||||
# app/health.py
|
||||
from flask import Blueprint, jsonify
|
||||
from app.extensions import db
|
||||
from sqlalchemy import text
|
||||
import redis
|
||||
import os
|
||||
|
||||
health_bp = Blueprint('health', __name__)
|
||||
|
||||
@health_bp.route('/health')
|
||||
def health_check():
|
||||
"""Application health check."""
|
||||
checks = {
|
||||
'status': 'healthy',
|
||||
'database': check_database(),
|
||||
'redis': check_redis(),
|
||||
'disk_space': check_disk_space()
|
||||
}
|
||||
|
||||
# Determine overall status
|
||||
if all(check['status'] == 'ok' for check in checks.values() if isinstance(check, dict)):
|
||||
status_code = 200
|
||||
else:
|
||||
status_code = 503
|
||||
checks['status'] = 'unhealthy'
|
||||
|
||||
return jsonify(checks), status_code
|
||||
|
||||
def check_database():
|
||||
"""Check database connectivity."""
|
||||
try:
|
||||
db.session.execute(text('SELECT 1'))
|
||||
return {'status': 'ok', 'message': 'Database connection successful'}
|
||||
except Exception as e:
|
||||
return {'status': 'error', 'message': str(e)}
|
||||
|
||||
def check_redis():
|
||||
"""Check Redis connectivity."""
|
||||
try:
|
||||
redis_url = os.environ.get('REDIS_URL', 'redis://localhost:6379/0')
|
||||
r = redis.from_url(redis_url)
|
||||
r.ping()
|
||||
return {'status': 'ok', 'message': 'Redis connection successful'}
|
||||
except Exception as e:
|
||||
return {'status': 'error', 'message': str(e)}
|
||||
|
||||
def check_disk_space():
|
||||
"""Check available disk space."""
|
||||
try:
|
||||
import shutil
|
||||
total, used, free = shutil.disk_usage('/')
|
||||
free_percent = (free / total) * 100
|
||||
|
||||
if free_percent > 10:
|
||||
status = 'ok'
|
||||
elif free_percent > 5:
|
||||
status = 'warning'
|
||||
else:
|
||||
status = 'critical'
|
||||
|
||||
return {
|
||||
'status': status,
|
||||
'free_space_percent': round(free_percent, 2),
|
||||
'free_space_gb': round(free / (1024**3), 2)
|
||||
}
|
||||
except Exception as e:
|
||||
return {'status': 'error', 'message': str(e)}
|
||||
```
|
||||
|
||||
## Monitoring and Logging
|
||||
|
||||
```python
|
||||
# app/logging.py
|
||||
import logging
|
||||
import logging.handlers
|
||||
import os
|
||||
from flask import request, g
|
||||
import time
|
||||
|
||||
def setup_logging(app):
|
||||
"""Setup application logging."""
|
||||
if not app.debug and not app.testing:
|
||||
# File logging
|
||||
if app.config.get('LOG_FILE'):
|
||||
file_handler = logging.handlers.RotatingFileHandler(
|
||||
app.config['LOG_FILE'],
|
||||
maxBytes=10240000, # 10MB
|
||||
backupCount=10
|
||||
)
|
||||
file_handler.setFormatter(logging.Formatter(
|
||||
'%(asctime)s %(levelname)s: %(message)s '
|
||||
'[in %(pathname)s:%(lineno)d]'
|
||||
))
|
||||
file_handler.setLevel(getattr(logging, app.config.get('LOG_LEVEL', 'INFO')))
|
||||
app.logger.addHandler(file_handler)
|
||||
|
||||
# Console logging
|
||||
if not app.logger.handlers:
|
||||
stream_handler = logging.StreamHandler()
|
||||
stream_handler.setFormatter(logging.Formatter(
|
||||
'%(asctime)s %(levelname)s: %(message)s'
|
||||
))
|
||||
stream_handler.setLevel(logging.INFO)
|
||||
app.logger.addHandler(stream_handler)
|
||||
|
||||
app.logger.setLevel(logging.INFO)
|
||||
app.logger.info('Application startup')
|
||||
|
||||
# Request timing middleware
|
||||
@app.before_request
|
||||
def before_request():
|
||||
g.start_time = time.time()
|
||||
|
||||
@app.after_request
|
||||
def after_request(response):
|
||||
if hasattr(g, 'start_time'):
|
||||
duration = time.time() - g.start_time
|
||||
app.logger.info(
|
||||
f'{request.method} {request.path} - '
|
||||
f'{response.status_code} - {duration:.3f}s'
|
||||
)
|
||||
return response
|
||||
```
|
||||
|
||||
## Database Backup Script
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# backup.sh
|
||||
|
||||
set -e
|
||||
|
||||
# Configuration
|
||||
BACKUP_DIR="/var/backups/db"
|
||||
DATABASE_URL="$DATABASE_URL"
|
||||
DATE=$(date +%Y%m%d_%H%M%S)
|
||||
BACKUP_FILE="$BACKUP_DIR/backup_$DATE.sql"
|
||||
RETENTION_DAYS=7
|
||||
|
||||
# Create backup directory
|
||||
mkdir -p "$BACKUP_DIR"
|
||||
|
||||
# Create backup
|
||||
echo "Creating database backup..."
|
||||
pg_dump "$DATABASE_URL" > "$BACKUP_FILE"
|
||||
|
||||
# Compress backup
|
||||
gzip "$BACKUP_FILE"
|
||||
|
||||
# Remove old backups
|
||||
echo "Cleaning up old backups..."
|
||||
find "$BACKUP_DIR" -name "backup_*.sql.gz" -mtime +$RETENTION_DAYS -delete
|
||||
|
||||
echo "Backup completed: $BACKUP_FILE.gz"
|
||||
```
|
||||
|
||||
## Deployment Scripts
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# deploy.sh
|
||||
|
||||
set -e
|
||||
|
||||
echo "Starting deployment..."
|
||||
|
||||
# Pull latest code
|
||||
git pull origin main
|
||||
|
||||
# Build new Docker image
|
||||
docker-compose build web
|
||||
|
||||
# Run database migrations
|
||||
docker-compose run --rm web flask db upgrade
|
||||
|
||||
# Update services
|
||||
docker-compose up -d
|
||||
|
||||
# Health check
|
||||
echo "Waiting for application to be ready..."
|
||||
sleep 10
|
||||
|
||||
if curl -f http://localhost:8000/health; then
|
||||
echo "Deployment successful!"
|
||||
else
|
||||
echo "Deployment failed - health check failed"
|
||||
exit 1
|
||||
fi
|
||||
```
|
||||
|
||||
## Monitoring with Prometheus
|
||||
|
||||
```python
|
||||
# app/metrics.py
|
||||
from prometheus_flask_exporter import PrometheusMetrics
|
||||
from flask import request
|
||||
import time
|
||||
|
||||
def setup_metrics(app):
|
||||
"""Setup Prometheus metrics."""
|
||||
metrics = PrometheusMetrics(app)
|
||||
|
||||
# Custom metrics
|
||||
metrics.info('app_info', 'Application info', version='1.0.0')
|
||||
|
||||
# Database connection pool metrics
|
||||
@metrics.gauge('db_pool_size', 'Database connection pool size')
|
||||
def db_pool_size():
|
||||
from app.extensions import db
|
||||
return db.engine.pool.size()
|
||||
|
||||
return metrics
|
||||
```
|
||||
@@ -0,0 +1,217 @@
|
||||
# Flask Route Generator
|
||||
|
||||
Create Flask routes with proper structure and error handling.
|
||||
|
||||
## Purpose
|
||||
|
||||
This command helps you quickly create Flask routes with validation, error handling, and best practices.
|
||||
|
||||
## Usage
|
||||
|
||||
```
|
||||
/flask-route
|
||||
```
|
||||
|
||||
## What this command does
|
||||
|
||||
1. **Creates route functions** with proper decorators
|
||||
2. **Adds request validation** and error handling
|
||||
3. **Includes JSON responses** and status codes
|
||||
4. **Implements authentication** if needed
|
||||
5. **Follows Flask conventions** and best practices
|
||||
|
||||
## Example Output
|
||||
|
||||
```python
|
||||
# routes.py or app.py
|
||||
from flask import Flask, request, jsonify, abort
|
||||
from flask_sqlalchemy import SQLAlchemy
|
||||
from werkzeug.exceptions import BadRequest
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
@app.route('/users', methods=['GET'])
|
||||
def get_users():
|
||||
"""Get all users with optional pagination."""
|
||||
try:
|
||||
page = request.args.get('page', 1, type=int)
|
||||
per_page = request.args.get('per_page', 10, type=int)
|
||||
|
||||
users = User.query.paginate(
|
||||
page=page,
|
||||
per_page=per_page,
|
||||
error_out=False
|
||||
)
|
||||
|
||||
return jsonify({
|
||||
'users': [user.to_dict() for user in users.items],
|
||||
'total': users.total,
|
||||
'pages': users.pages,
|
||||
'current_page': page
|
||||
}), 200
|
||||
|
||||
except Exception as e:
|
||||
return jsonify({'error': 'Failed to fetch users'}), 500
|
||||
|
||||
@app.route('/users/<int:user_id>', methods=['GET'])
|
||||
def get_user(user_id):
|
||||
"""Get a specific user by ID."""
|
||||
try:
|
||||
user = User.query.get_or_404(user_id)
|
||||
return jsonify(user.to_dict()), 200
|
||||
|
||||
except Exception as e:
|
||||
return jsonify({'error': 'User not found'}), 404
|
||||
|
||||
@app.route('/users', methods=['POST'])
|
||||
def create_user():
|
||||
"""Create a new user."""
|
||||
try:
|
||||
data = request.get_json()
|
||||
|
||||
if not data:
|
||||
return jsonify({'error': 'No data provided'}), 400
|
||||
|
||||
# Validate required fields
|
||||
required_fields = ['name', 'email']
|
||||
for field in required_fields:
|
||||
if field not in data:
|
||||
return jsonify({'error': f'{field} is required'}), 400
|
||||
|
||||
# Check if email already exists
|
||||
if User.query.filter_by(email=data['email']).first():
|
||||
return jsonify({'error': 'Email already exists'}), 409
|
||||
|
||||
# Create new user
|
||||
user = User(
|
||||
name=data['name'],
|
||||
email=data['email'],
|
||||
phone=data.get('phone'),
|
||||
address=data.get('address')
|
||||
)
|
||||
|
||||
db.session.add(user)
|
||||
db.session.commit()
|
||||
|
||||
return jsonify(user.to_dict()), 201
|
||||
|
||||
except BadRequest:
|
||||
return jsonify({'error': 'Invalid JSON data'}), 400
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
return jsonify({'error': 'Failed to create user'}), 500
|
||||
|
||||
@app.route('/users/<int:user_id>', methods=['PUT'])
|
||||
def update_user(user_id):
|
||||
"""Update an existing user."""
|
||||
try:
|
||||
user = User.query.get_or_404(user_id)
|
||||
data = request.get_json()
|
||||
|
||||
if not data:
|
||||
return jsonify({'error': 'No data provided'}), 400
|
||||
|
||||
# Update fields
|
||||
if 'name' in data:
|
||||
user.name = data['name']
|
||||
if 'email' in data:
|
||||
# Check if new email already exists
|
||||
existing_user = User.query.filter_by(email=data['email']).first()
|
||||
if existing_user and existing_user.id != user_id:
|
||||
return jsonify({'error': 'Email already exists'}), 409
|
||||
user.email = data['email']
|
||||
if 'phone' in data:
|
||||
user.phone = data['phone']
|
||||
if 'address' in data:
|
||||
user.address = data['address']
|
||||
|
||||
db.session.commit()
|
||||
|
||||
return jsonify(user.to_dict()), 200
|
||||
|
||||
except BadRequest:
|
||||
return jsonify({'error': 'Invalid JSON data'}), 400
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
return jsonify({'error': 'Failed to update user'}), 500
|
||||
|
||||
@app.route('/users/<int:user_id>', methods=['DELETE'])
|
||||
def delete_user(user_id):
|
||||
"""Delete a user."""
|
||||
try:
|
||||
user = User.query.get_or_404(user_id)
|
||||
|
||||
db.session.delete(user)
|
||||
db.session.commit()
|
||||
|
||||
return jsonify({'message': 'User deleted successfully'}), 200
|
||||
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
return jsonify({'error': 'Failed to delete user'}), 500
|
||||
|
||||
# Error handlers
|
||||
@app.errorhandler(404)
|
||||
def not_found(error):
|
||||
return jsonify({'error': 'Resource not found'}), 404
|
||||
|
||||
@app.errorhandler(400)
|
||||
def bad_request(error):
|
||||
return jsonify({'error': 'Bad request'}), 400
|
||||
|
||||
@app.errorhandler(500)
|
||||
def internal_error(error):
|
||||
return jsonify({'error': 'Internal server error'}), 500
|
||||
```
|
||||
|
||||
## Route Patterns Supported
|
||||
|
||||
### Basic Routes
|
||||
```python
|
||||
@app.route('/')
|
||||
@app.route('/users')
|
||||
@app.route('/users/<int:user_id>')
|
||||
```
|
||||
|
||||
### HTTP Methods
|
||||
```python
|
||||
@app.route('/users', methods=['GET', 'POST'])
|
||||
@app.route('/users/<int:id>', methods=['GET', 'PUT', 'DELETE'])
|
||||
```
|
||||
|
||||
### URL Parameters
|
||||
```python
|
||||
@app.route('/users/<int:user_id>')
|
||||
@app.route('/posts/<string:slug>')
|
||||
@app.route('/files/<path:filename>')
|
||||
```
|
||||
|
||||
## Best Practices Included
|
||||
|
||||
- **Input validation** for all user data
|
||||
- **Proper HTTP status codes** (200, 201, 400, 404, 500)
|
||||
- **JSON responses** with consistent structure
|
||||
- **Error handling** with try/catch blocks
|
||||
- **Database rollback** on errors
|
||||
- **RESTful conventions** for URL design
|
||||
- **Documentation strings** for each route
|
||||
- **Request data validation** before processing
|
||||
|
||||
## Common Response Patterns
|
||||
|
||||
```python
|
||||
# Success with data
|
||||
return jsonify({'data': result}), 200
|
||||
|
||||
# Created resource
|
||||
return jsonify({'data': new_resource, 'id': new_id}), 201
|
||||
|
||||
# Validation error
|
||||
return jsonify({'error': 'Field is required'}), 400
|
||||
|
||||
# Not found
|
||||
return jsonify({'error': 'Resource not found'}), 404
|
||||
|
||||
# Server error
|
||||
return jsonify({'error': 'Internal server error'}), 500
|
||||
```
|
||||
@@ -0,0 +1,559 @@
|
||||
# Flask Testing Suite
|
||||
|
||||
Comprehensive testing setup for Flask applications with pytest.
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
# Run all tests
|
||||
pytest
|
||||
|
||||
# Run with coverage
|
||||
pytest --cov=app --cov-report=html
|
||||
|
||||
# Run specific test file
|
||||
pytest tests/test_models.py
|
||||
|
||||
# Run with verbose output
|
||||
pytest -v
|
||||
```
|
||||
|
||||
## Test Configuration
|
||||
|
||||
```python
|
||||
# pytest.ini
|
||||
[tool:pytest]
|
||||
testpaths = tests
|
||||
python_files = test_*.py
|
||||
python_classes = Test*
|
||||
python_functions = test_*
|
||||
addopts =
|
||||
--cov=app
|
||||
--cov-report=term-missing
|
||||
--cov-report=html:htmlcov
|
||||
--strict-markers
|
||||
--disable-warnings
|
||||
markers =
|
||||
unit: Unit tests
|
||||
integration: Integration tests
|
||||
slow: Slow running tests
|
||||
auth: Authentication tests
|
||||
```
|
||||
|
||||
## Test Fixtures
|
||||
|
||||
```python
|
||||
# tests/conftest.py
|
||||
import pytest
|
||||
import tempfile
|
||||
import os
|
||||
from app import create_app
|
||||
from app.extensions import db
|
||||
from app.models import User, Post, Category
|
||||
from flask_login import login_user
|
||||
|
||||
@pytest.fixture(scope='session')
|
||||
def app():
|
||||
"""Create test application."""
|
||||
# Create temporary database
|
||||
db_fd, db_path = tempfile.mkstemp()
|
||||
|
||||
app = create_app({
|
||||
'TESTING': True,
|
||||
'SQLALCHEMY_DATABASE_URI': f'sqlite:///{db_path}',
|
||||
'WTF_CSRF_ENABLED': False,
|
||||
'SECRET_KEY': 'test-secret-key'
|
||||
})
|
||||
|
||||
with app.app_context():
|
||||
db.create_all()
|
||||
yield app
|
||||
|
||||
# Cleanup
|
||||
os.close(db_fd)
|
||||
os.unlink(db_path)
|
||||
|
||||
@pytest.fixture
|
||||
def client(app):
|
||||
"""Create test client."""
|
||||
return app.test_client()
|
||||
|
||||
@pytest.fixture
|
||||
def runner(app):
|
||||
"""Create test CLI runner."""
|
||||
return app.test_cli_runner()
|
||||
|
||||
@pytest.fixture
|
||||
def db_session(app):
|
||||
"""Create database session for testing."""
|
||||
with app.app_context():
|
||||
connection = db.engine.connect()
|
||||
transaction = connection.begin()
|
||||
|
||||
# Configure session to use the connection
|
||||
db.session.configure(bind=connection)
|
||||
|
||||
yield db.session
|
||||
|
||||
# Rollback transaction
|
||||
transaction.rollback()
|
||||
connection.close()
|
||||
db.session.remove()
|
||||
|
||||
@pytest.fixture
|
||||
def user(db_session):
|
||||
"""Create test user."""
|
||||
user = User(
|
||||
username='testuser',
|
||||
email='test@example.com',
|
||||
first_name='Test',
|
||||
last_name='User'
|
||||
)
|
||||
user.set_password('testpass123')
|
||||
user.save()
|
||||
return user
|
||||
|
||||
@pytest.fixture
|
||||
def admin_user(db_session):
|
||||
"""Create admin user."""
|
||||
admin = User(
|
||||
username='admin',
|
||||
email='admin@example.com',
|
||||
first_name='Admin',
|
||||
last_name='User',
|
||||
is_admin=True
|
||||
)
|
||||
admin.set_password('adminpass123')
|
||||
admin.save()
|
||||
return admin
|
||||
|
||||
@pytest.fixture
|
||||
def category(db_session):
|
||||
"""Create test category."""
|
||||
category = Category(
|
||||
name='Test Category',
|
||||
description='A test category'
|
||||
)
|
||||
category.save()
|
||||
return category
|
||||
|
||||
@pytest.fixture
|
||||
def post(db_session, user, category):
|
||||
"""Create test post."""
|
||||
post = Post(
|
||||
title='Test Post',
|
||||
content='This is a test post content.',
|
||||
slug='test-post',
|
||||
status='published',
|
||||
user_id=user.id,
|
||||
category_id=category.id
|
||||
)
|
||||
post.save()
|
||||
return post
|
||||
|
||||
@pytest.fixture
|
||||
def auth_headers(user):
|
||||
"""Create authentication headers."""
|
||||
# For API testing
|
||||
token = user.generate_auth_token()
|
||||
return {'Authorization': f'Bearer {token}'}
|
||||
```
|
||||
|
||||
## Model Testing
|
||||
|
||||
```python
|
||||
# tests/test_models.py
|
||||
import pytest
|
||||
from datetime import datetime
|
||||
from app.models import User, Post, Category
|
||||
from werkzeug.security import check_password_hash
|
||||
|
||||
class TestUser:
|
||||
"""Test User model."""
|
||||
|
||||
def test_user_creation(self, db_session):
|
||||
"""Test user creation."""
|
||||
user = User(
|
||||
username='newuser',
|
||||
email='new@example.com',
|
||||
first_name='New',
|
||||
last_name='User'
|
||||
)
|
||||
user.set_password('password123')
|
||||
user.save()
|
||||
|
||||
assert user.id is not None
|
||||
assert user.username == 'newuser'
|
||||
assert user.email == 'new@example.com'
|
||||
assert user.full_name == 'New User'
|
||||
assert user.is_active is True
|
||||
assert user.is_admin is False
|
||||
assert user.created_at is not None
|
||||
|
||||
def test_password_hashing(self, user):
|
||||
"""Test password hashing."""
|
||||
user.set_password('newpassword')
|
||||
assert user.password_hash != 'newpassword'
|
||||
assert check_password_hash(user.password_hash, 'newpassword')
|
||||
assert user.check_password('newpassword')
|
||||
assert not user.check_password('wrongpassword')
|
||||
|
||||
def test_user_repr(self, user):
|
||||
"""Test user string representation."""
|
||||
assert repr(user) == '<User testuser>'
|
||||
|
||||
def test_user_to_dict(self, user):
|
||||
"""Test user dictionary conversion."""
|
||||
user_dict = user.to_dict()
|
||||
assert 'username' in user_dict
|
||||
assert 'email' in user_dict
|
||||
assert 'password_hash' not in user_dict # Should be excluded
|
||||
|
||||
def test_user_relationships(self, user, post):
|
||||
"""Test user relationships."""
|
||||
assert post in user.posts
|
||||
assert user.posts.count() == 1
|
||||
|
||||
class TestPost:
|
||||
"""Test Post model."""
|
||||
|
||||
def test_post_creation(self, db_session, user, category):
|
||||
"""Test post creation."""
|
||||
post = Post(
|
||||
title='New Post',
|
||||
content='New post content',
|
||||
slug='new-post',
|
||||
status='draft',
|
||||
user_id=user.id,
|
||||
category_id=category.id
|
||||
)
|
||||
post.save()
|
||||
|
||||
assert post.id is not None
|
||||
assert post.title == 'New Post'
|
||||
assert post.author == user
|
||||
assert post.category == category
|
||||
assert not post.is_published
|
||||
|
||||
def test_published_status(self, post):
|
||||
"""Test post published status."""
|
||||
assert post.is_published # Published with published_at
|
||||
|
||||
post.status = 'draft'
|
||||
assert not post.is_published
|
||||
```
|
||||
|
||||
## View Testing
|
||||
|
||||
```python
|
||||
# tests/test_views.py
|
||||
import pytest
|
||||
from flask import url_for
|
||||
from app.models import User
|
||||
|
||||
class TestMainViews:
|
||||
"""Test main application views."""
|
||||
|
||||
def test_home_page(self, client):
|
||||
"""Test home page."""
|
||||
response = client.get('/')
|
||||
assert response.status_code == 200
|
||||
assert b'Welcome' in response.data
|
||||
|
||||
def test_about_page(self, client):
|
||||
"""Test about page."""
|
||||
response = client.get('/about')
|
||||
assert response.status_code == 200
|
||||
|
||||
class TestUserViews:
|
||||
"""Test user-related views."""
|
||||
|
||||
def test_user_list(self, client, user):
|
||||
"""Test user list page."""
|
||||
response = client.get('/users/')
|
||||
assert response.status_code == 200
|
||||
assert user.username.encode() in response.data
|
||||
|
||||
def test_user_detail(self, client, user):
|
||||
"""Test user detail page."""
|
||||
response = client.get(f'/users/{user.id}')
|
||||
assert response.status_code == 200
|
||||
assert user.username.encode() in response.data
|
||||
|
||||
def test_user_create_get(self, client):
|
||||
"""Test user creation form."""
|
||||
response = client.get('/users/create')
|
||||
assert response.status_code == 200
|
||||
assert b'Create User' in response.data
|
||||
|
||||
def test_user_create_post(self, client, db_session):
|
||||
"""Test user creation submission."""
|
||||
data = {
|
||||
'username': 'newuser',
|
||||
'email': 'new@example.com',
|
||||
'first_name': 'New',
|
||||
'last_name': 'User',
|
||||
'password': 'password123',
|
||||
'confirm_password': 'password123'
|
||||
}
|
||||
response = client.post('/users/create', data=data, follow_redirects=True)
|
||||
assert response.status_code == 200
|
||||
|
||||
# Check user was created
|
||||
user = User.query.filter_by(username='newuser').first()
|
||||
assert user is not None
|
||||
assert user.email == 'new@example.com'
|
||||
|
||||
def test_user_edit(self, client, user):
|
||||
"""Test user editing."""
|
||||
data = {
|
||||
'username': user.username,
|
||||
'email': 'updated@example.com',
|
||||
'first_name': 'Updated',
|
||||
'last_name': 'User'
|
||||
}
|
||||
response = client.post(f'/users/{user.id}/edit', data=data, follow_redirects=True)
|
||||
assert response.status_code == 200
|
||||
|
||||
# Refresh user from database
|
||||
db_session.refresh(user)
|
||||
assert user.email == 'updated@example.com'
|
||||
assert user.first_name == 'Updated'
|
||||
```
|
||||
|
||||
## Authentication Testing
|
||||
|
||||
```python
|
||||
# tests/test_auth.py
|
||||
import pytest
|
||||
from flask import url_for
|
||||
from app.models import User
|
||||
|
||||
class TestAuthentication:
|
||||
"""Test authentication functionality."""
|
||||
|
||||
def test_login_page(self, client):
|
||||
"""Test login page access."""
|
||||
response = client.get('/auth/login')
|
||||
assert response.status_code == 200
|
||||
assert b'Login' in response.data
|
||||
|
||||
def test_valid_login(self, client, user):
|
||||
"""Test valid user login."""
|
||||
data = {
|
||||
'username': user.username,
|
||||
'password': 'testpass123'
|
||||
}
|
||||
response = client.post('/auth/login', data=data, follow_redirects=True)
|
||||
assert response.status_code == 200
|
||||
assert b'Welcome' in response.data
|
||||
|
||||
def test_invalid_login(self, client, user):
|
||||
"""Test invalid login credentials."""
|
||||
data = {
|
||||
'username': user.username,
|
||||
'password': 'wrongpassword'
|
||||
}
|
||||
response = client.post('/auth/login', data=data)
|
||||
assert response.status_code == 200
|
||||
assert b'Invalid' in response.data
|
||||
|
||||
def test_logout(self, client, user):
|
||||
"""Test user logout."""
|
||||
# Login first
|
||||
with client.session_transaction() as sess:
|
||||
sess['_user_id'] = str(user.id)
|
||||
|
||||
response = client.get('/auth/logout', follow_redirects=True)
|
||||
assert response.status_code == 200
|
||||
|
||||
def test_register_page(self, client):
|
||||
"""Test registration page."""
|
||||
response = client.get('/auth/register')
|
||||
assert response.status_code == 200
|
||||
assert b'Register' in response.data
|
||||
|
||||
def test_valid_registration(self, client, db_session):
|
||||
"""Test valid user registration."""
|
||||
data = {
|
||||
'username': 'newuser',
|
||||
'email': 'new@example.com',
|
||||
'first_name': 'New',
|
||||
'last_name': 'User',
|
||||
'password': 'password123',
|
||||
'confirm_password': 'password123'
|
||||
}
|
||||
response = client.post('/auth/register', data=data, follow_redirects=True)
|
||||
assert response.status_code == 200
|
||||
|
||||
# Check user was created
|
||||
user = User.query.filter_by(username='newuser').first()
|
||||
assert user is not None
|
||||
```
|
||||
|
||||
## API Testing
|
||||
|
||||
```python
|
||||
# tests/test_api.py
|
||||
import pytest
|
||||
import json
|
||||
from flask import url_for
|
||||
|
||||
class TestUserAPI:
|
||||
"""Test User API endpoints."""
|
||||
|
||||
def test_get_users(self, client, user):
|
||||
"""Test GET /api/users."""
|
||||
response = client.get('/api/users')
|
||||
assert response.status_code == 200
|
||||
|
||||
data = json.loads(response.data)
|
||||
assert 'users' in data
|
||||
assert len(data['users']) >= 1
|
||||
|
||||
def test_get_user(self, client, user):
|
||||
"""Test GET /api/users/<id>."""
|
||||
response = client.get(f'/api/users/{user.id}')
|
||||
assert response.status_code == 200
|
||||
|
||||
data = json.loads(response.data)
|
||||
assert data['username'] == user.username
|
||||
assert data['email'] == user.email
|
||||
|
||||
def test_create_user(self, client, db_session):
|
||||
"""Test POST /api/users."""
|
||||
user_data = {
|
||||
'username': 'apiuser',
|
||||
'email': 'api@example.com',
|
||||
'first_name': 'API',
|
||||
'last_name': 'User',
|
||||
'password': 'password123'
|
||||
}
|
||||
|
||||
response = client.post(
|
||||
'/api/users',
|
||||
data=json.dumps(user_data),
|
||||
content_type='application/json'
|
||||
)
|
||||
|
||||
assert response.status_code == 201
|
||||
data = json.loads(response.data)
|
||||
assert data['username'] == 'apiuser'
|
||||
|
||||
def test_update_user(self, client, user, auth_headers):
|
||||
"""Test PUT /api/users/<id>."""
|
||||
update_data = {
|
||||
'email': 'updated@example.com',
|
||||
'first_name': 'Updated'
|
||||
}
|
||||
|
||||
response = client.put(
|
||||
f'/api/users/{user.id}',
|
||||
data=json.dumps(update_data),
|
||||
content_type='application/json',
|
||||
headers=auth_headers
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
data = json.loads(response.data)
|
||||
assert data['email'] == 'updated@example.com'
|
||||
|
||||
def test_delete_user(self, client, user, auth_headers):
|
||||
"""Test DELETE /api/users/<id>."""
|
||||
response = client.delete(
|
||||
f'/api/users/{user.id}',
|
||||
headers=auth_headers
|
||||
)
|
||||
|
||||
assert response.status_code == 204
|
||||
```
|
||||
|
||||
## Test Utilities
|
||||
|
||||
```python
|
||||
# tests/utils.py
|
||||
import json
|
||||
from flask import url_for
|
||||
|
||||
def login_user(client, username, password):
|
||||
"""Helper to login user in tests."""
|
||||
return client.post('/auth/login', data={
|
||||
'username': username,
|
||||
'password': password
|
||||
}, follow_redirects=True)
|
||||
|
||||
def logout_user(client):
|
||||
"""Helper to logout user in tests."""
|
||||
return client.get('/auth/logout', follow_redirects=True)
|
||||
|
||||
def assert_json_response(response, expected_status=200):
|
||||
"""Assert JSON response format and status."""
|
||||
assert response.status_code == expected_status
|
||||
assert response.content_type == 'application/json'
|
||||
return json.loads(response.data)
|
||||
|
||||
def create_test_data(db_session):
|
||||
"""Create common test data."""
|
||||
from app.models import User, Category, Post
|
||||
|
||||
# Create test users
|
||||
users = []
|
||||
for i in range(3):
|
||||
user = User(
|
||||
username=f'user{i}',
|
||||
email=f'user{i}@example.com',
|
||||
first_name=f'User{i}',
|
||||
last_name='Test'
|
||||
)
|
||||
user.set_password('password123')
|
||||
user.save()
|
||||
users.append(user)
|
||||
|
||||
return {'users': users}
|
||||
```
|
||||
|
||||
## Performance Testing
|
||||
|
||||
```python
|
||||
# tests/test_performance.py
|
||||
import pytest
|
||||
import time
|
||||
from app.models import User
|
||||
|
||||
@pytest.mark.slow
|
||||
class TestPerformance:
|
||||
"""Test application performance."""
|
||||
|
||||
def test_user_query_performance(self, db_session):
|
||||
"""Test user query performance."""
|
||||
# Create multiple users
|
||||
users = []
|
||||
for i in range(100):
|
||||
user = User(
|
||||
username=f'perfuser{i}',
|
||||
email=f'perf{i}@example.com',
|
||||
first_name=f'Perf{i}',
|
||||
last_name='User'
|
||||
)
|
||||
users.append(user)
|
||||
|
||||
db_session.bulk_save_objects(users)
|
||||
db_session.commit()
|
||||
|
||||
# Test query performance
|
||||
start_time = time.time()
|
||||
result = User.query.all()
|
||||
end_time = time.time()
|
||||
|
||||
assert len(result) >= 100
|
||||
assert (end_time - start_time) < 0.1 # Should complete in under 100ms
|
||||
|
||||
def test_endpoint_response_time(self, client):
|
||||
"""Test endpoint response time."""
|
||||
start_time = time.time()
|
||||
response = client.get('/')
|
||||
end_time = time.time()
|
||||
|
||||
assert response.status_code == 200
|
||||
assert (end_time - start_time) < 0.5 # Should respond in under 500ms
|
||||
```
|
||||
@@ -0,0 +1,391 @@
|
||||
# Flask Project Configuration
|
||||
|
||||
This file provides specific guidance for Flask web application development using Claude Code.
|
||||
|
||||
## Project Overview
|
||||
|
||||
This is a Flask web application project optimized for scalable web development with the Flask micro-framework. The project follows Flask best practices and modern Python development patterns.
|
||||
|
||||
## Flask-Specific Development Commands
|
||||
|
||||
### Project Management
|
||||
- `flask run` - Start development server
|
||||
- `flask run --host=0.0.0.0 --port=5000` - Start server accessible from network
|
||||
- `flask shell` - Open Flask shell with application context
|
||||
- `python -m flask --help` - Show available Flask commands
|
||||
|
||||
### Database Management
|
||||
- `flask db init` - Initialize database migrations
|
||||
- `flask db migrate -m "message"` - Create database migration
|
||||
- `flask db upgrade` - Apply database migrations
|
||||
- `flask db downgrade` - Rollback database migration
|
||||
- `flask db current` - Show current migration
|
||||
- `flask db history` - Show migration history
|
||||
|
||||
### Development Tools
|
||||
- `flask routes` - Show all registered routes
|
||||
- `flask --version` - Show Flask version
|
||||
- `export FLASK_ENV=development` - Set development environment
|
||||
- `export FLASK_DEBUG=1` - Enable debug mode
|
||||
|
||||
### Custom Commands
|
||||
- `flask init-db` - Initialize database with tables
|
||||
- `flask seed-db` - Seed database with sample data
|
||||
- `flask reset-db` - Reset database (development only)
|
||||
|
||||
## Flask Project Structure
|
||||
|
||||
```
|
||||
myproject/
|
||||
├── app/ # Application package
|
||||
│ ├── __init__.py # Application factory
|
||||
│ ├── extensions.py # Flask extensions
|
||||
│ ├── config.py # Configuration settings
|
||||
│ ├── models/ # Database models
|
||||
│ │ ├── __init__.py
|
||||
│ │ ├── base.py # Base model class
|
||||
│ │ ├── user.py # User model
|
||||
│ │ └── post.py # Post model
|
||||
│ ├── blueprints/ # Application blueprints
|
||||
│ │ ├── __init__.py
|
||||
│ │ ├── main/ # Main blueprint
|
||||
│ │ ├── auth/ # Authentication blueprint
|
||||
│ │ ├── api/ # API blueprint
|
||||
│ │ └── admin/ # Admin blueprint
|
||||
│ ├── templates/ # Jinja2 templates
|
||||
│ │ ├── base.html
|
||||
│ │ ├── index.html
|
||||
│ │ └── auth/
|
||||
│ ├── static/ # Static files
|
||||
│ │ ├── css/
|
||||
│ │ ├── js/
|
||||
│ │ └── images/
|
||||
│ ├── forms/ # WTForms
|
||||
│ │ ├── __init__.py
|
||||
│ │ ├── auth.py
|
||||
│ │ └── user.py
|
||||
│ ├── utils/ # Utility functions
|
||||
│ └── cli.py # Custom CLI commands
|
||||
├── migrations/ # Database migrations
|
||||
├── tests/ # Test files
|
||||
│ ├── conftest.py
|
||||
│ ├── test_models.py
|
||||
│ ├── test_views.py
|
||||
│ └── test_api.py
|
||||
├── requirements/ # Requirements files
|
||||
│ ├── base.txt
|
||||
│ ├── development.txt
|
||||
│ └── production.txt
|
||||
├── wsgi.py # WSGI entry point
|
||||
├── gunicorn.conf.py # Gunicorn configuration
|
||||
└── docker-compose.yml # Docker Compose configuration
|
||||
```
|
||||
|
||||
## Flask Application Factory
|
||||
|
||||
```python
|
||||
# app/__init__.py
|
||||
from flask import Flask
|
||||
from app.extensions import db, migrate, login_manager, csrf, cache
|
||||
from app.config import config
|
||||
|
||||
def create_app(config_name='default'):
|
||||
app = Flask(__name__)
|
||||
app.config.from_object(config[config_name])
|
||||
|
||||
# Initialize extensions
|
||||
db.init_app(app)
|
||||
migrate.init_app(app, db)
|
||||
login_manager.init_app(app)
|
||||
csrf.init_app(app)
|
||||
cache.init_app(app)
|
||||
|
||||
# Register blueprints
|
||||
from app.blueprints.main import main_bp
|
||||
from app.blueprints.auth import auth_bp
|
||||
from app.blueprints.api import api_bp
|
||||
|
||||
app.register_blueprint(main_bp)
|
||||
app.register_blueprint(auth_bp, url_prefix='/auth')
|
||||
app.register_blueprint(api_bp, url_prefix='/api/v1')
|
||||
|
||||
# Register CLI commands
|
||||
from app.cli import init_commands
|
||||
init_commands(app)
|
||||
|
||||
return app
|
||||
```
|
||||
|
||||
## Configuration Management
|
||||
|
||||
```python
|
||||
# app/config.py
|
||||
import os
|
||||
from datetime import timedelta
|
||||
|
||||
class Config:
|
||||
SECRET_KEY = os.environ.get('SECRET_KEY') or 'dev-secret-key'
|
||||
SQLALCHEMY_TRACK_MODIFICATIONS = False
|
||||
SQLALCHEMY_RECORD_QUERIES = True
|
||||
|
||||
# Session configuration
|
||||
PERMANENT_SESSION_LIFETIME = timedelta(hours=1)
|
||||
SESSION_COOKIE_SECURE = True
|
||||
SESSION_COOKIE_HTTPONLY = True
|
||||
SESSION_COOKIE_SAMESITE = 'Lax'
|
||||
|
||||
# File upload
|
||||
MAX_CONTENT_LENGTH = 16 * 1024 * 1024 # 16MB
|
||||
UPLOAD_FOLDER = os.path.join(os.getcwd(), 'uploads')
|
||||
|
||||
# Cache
|
||||
CACHE_TYPE = 'simple'
|
||||
CACHE_DEFAULT_TIMEOUT = 300
|
||||
|
||||
class DevelopmentConfig(Config):
|
||||
DEBUG = True
|
||||
SQLALCHEMY_DATABASE_URI = os.environ.get('DEV_DATABASE_URL') or \
|
||||
'sqlite:///dev.db'
|
||||
SESSION_COOKIE_SECURE = False
|
||||
|
||||
class ProductionConfig(Config):
|
||||
DEBUG = False
|
||||
SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL')
|
||||
|
||||
# Security headers
|
||||
SECURITY_HEADERS = {
|
||||
'Strict-Transport-Security': 'max-age=31536000; includeSubDomains',
|
||||
'X-Content-Type-Options': 'nosniff',
|
||||
'X-Frame-Options': 'DENY',
|
||||
'X-XSS-Protection': '1; mode=block'
|
||||
}
|
||||
|
||||
class TestingConfig(Config):
|
||||
TESTING = True
|
||||
SQLALCHEMY_DATABASE_URI = 'sqlite:///:memory:'
|
||||
WTF_CSRF_ENABLED = False
|
||||
|
||||
config = {
|
||||
'development': DevelopmentConfig,
|
||||
'production': ProductionConfig,
|
||||
'testing': TestingConfig,
|
||||
'default': DevelopmentConfig
|
||||
}
|
||||
```
|
||||
|
||||
## Flask Best Practices
|
||||
|
||||
### Application Structure
|
||||
- Use application factory pattern for configuration flexibility
|
||||
- Organize code into blueprints for modularity
|
||||
- Separate models, views, and forms into different modules
|
||||
- Use extensions.py to initialize Flask extensions
|
||||
- Implement proper error handling and logging
|
||||
|
||||
### Database Models
|
||||
- Use SQLAlchemy ORM for database operations
|
||||
- Implement base model with common functionality
|
||||
- Add proper relationships between models
|
||||
- Use database migrations for schema changes
|
||||
- Implement model validation and constraints
|
||||
|
||||
### Blueprint Organization
|
||||
- Group related functionality into blueprints
|
||||
- Use URL prefixes for namespacing
|
||||
- Implement blueprint-specific templates
|
||||
- Add proper error handlers for each blueprint
|
||||
- Use blueprint factories for complex blueprints
|
||||
|
||||
### Template Management
|
||||
- Use template inheritance for consistent layout
|
||||
- Create reusable template macros
|
||||
- Implement proper CSRF protection in forms
|
||||
- Use Flask-WTF for form handling and validation
|
||||
- Organize templates by blueprint
|
||||
|
||||
### Security Considerations
|
||||
- Always validate and sanitize user input
|
||||
- Use Flask-Login for user session management
|
||||
- Implement proper authentication and authorization
|
||||
- Use CSRF protection for all forms
|
||||
- Set secure session cookie configuration
|
||||
- Implement rate limiting for API endpoints
|
||||
|
||||
## Flask Extensions
|
||||
|
||||
### Essential Extensions
|
||||
```python
|
||||
# app/extensions.py
|
||||
from flask_sqlalchemy import SQLAlchemy
|
||||
from flask_migrate import Migrate
|
||||
from flask_login import LoginManager
|
||||
from flask_wtf.csrf import CSRFProtect
|
||||
from flask_caching import Cache
|
||||
from flask_limiter import Limiter
|
||||
from flask_limiter.util import get_remote_address
|
||||
|
||||
db = SQLAlchemy()
|
||||
migrate = Migrate()
|
||||
login_manager = LoginManager()
|
||||
csrf = CSRFProtect()
|
||||
cache = Cache()
|
||||
limiter = Limiter(key_func=get_remote_address)
|
||||
```
|
||||
|
||||
### Recommended Extensions
|
||||
- **Flask-SQLAlchemy** - Database ORM
|
||||
- **Flask-Migrate** - Database migrations
|
||||
- **Flask-Login** - User session management
|
||||
- **Flask-WTF** - Form handling and CSRF protection
|
||||
- **Flask-Caching** - Caching support
|
||||
- **Flask-Limiter** - Rate limiting
|
||||
- **Flask-Mail** - Email support
|
||||
- **Flask-Admin** - Admin interface
|
||||
|
||||
## Testing Strategy
|
||||
|
||||
### Test Organization
|
||||
```python
|
||||
# tests/conftest.py
|
||||
import pytest
|
||||
from app import create_app
|
||||
from app.extensions import db
|
||||
|
||||
@pytest.fixture(scope='session')
|
||||
def app():
|
||||
app = create_app('testing')
|
||||
with app.app_context():
|
||||
db.create_all()
|
||||
yield app
|
||||
db.drop_all()
|
||||
|
||||
@pytest.fixture
|
||||
def client(app):
|
||||
return app.test_client()
|
||||
|
||||
@pytest.fixture
|
||||
def runner(app):
|
||||
return app.test_cli_runner()
|
||||
```
|
||||
|
||||
### Test Types
|
||||
- **Unit tests** for models and utilities
|
||||
- **Integration tests** for views and API endpoints
|
||||
- **Functional tests** for user workflows
|
||||
- **Performance tests** for critical paths
|
||||
|
||||
### Testing Best Practices
|
||||
- Use fixtures for common test data
|
||||
- Test both success and error conditions
|
||||
- Mock external dependencies
|
||||
- Use factory_boy for test data generation
|
||||
- Implement database transaction rollback in tests
|
||||
|
||||
## Performance Optimization
|
||||
|
||||
### Database Optimization
|
||||
- Use connection pooling for production
|
||||
- Implement query optimization with indexes
|
||||
- Use lazy loading for relationships
|
||||
- Cache frequently accessed data
|
||||
- Monitor database query performance
|
||||
|
||||
### Caching Strategy
|
||||
- Implement Redis for session storage
|
||||
- Use view-level caching for static content
|
||||
- Cache database query results
|
||||
- Implement cache invalidation strategies
|
||||
- Use CDN for static files
|
||||
|
||||
### Application Optimization
|
||||
- Use Gunicorn with multiple workers
|
||||
- Implement proper logging and monitoring
|
||||
- Optimize static file serving
|
||||
- Use async tasks for long-running operations
|
||||
- Implement proper error handling
|
||||
|
||||
## Deployment Considerations
|
||||
|
||||
### Production Setup
|
||||
- Use environment variables for configuration
|
||||
- Implement proper logging and monitoring
|
||||
- Set up database connection pooling
|
||||
- Configure reverse proxy (Nginx)
|
||||
- Use HTTPS with proper SSL certificates
|
||||
|
||||
### Docker Configuration
|
||||
```dockerfile
|
||||
FROM python:3.11-slim
|
||||
WORKDIR /app
|
||||
COPY requirements/production.txt requirements.txt
|
||||
RUN pip install -r requirements.txt
|
||||
COPY . .
|
||||
EXPOSE 5000
|
||||
CMD ["gunicorn", "--config", "gunicorn.conf.py", "wsgi:app"]
|
||||
```
|
||||
|
||||
### Environment Variables
|
||||
```bash
|
||||
FLASK_ENV=production
|
||||
SECRET_KEY=your-secret-key
|
||||
DATABASE_URL=postgresql://user:pass@host:port/db
|
||||
REDIS_URL=redis://host:port/db
|
||||
```
|
||||
|
||||
## Common Flask Patterns
|
||||
|
||||
### Custom Decorators
|
||||
```python
|
||||
from functools import wraps
|
||||
from flask import abort
|
||||
from flask_login import current_user
|
||||
|
||||
def admin_required(f):
|
||||
@wraps(f)
|
||||
def decorated_function(*args, **kwargs):
|
||||
if not current_user.is_admin:
|
||||
abort(403)
|
||||
return f(*args, **kwargs)
|
||||
return decorated_function
|
||||
```
|
||||
|
||||
### Request Context Processors
|
||||
```python
|
||||
@app.context_processor
|
||||
def inject_user():
|
||||
return dict(current_user=current_user)
|
||||
```
|
||||
|
||||
### Custom Filters
|
||||
```python
|
||||
@app.template_filter('datetime')
|
||||
def datetime_filter(value, format='%Y-%m-%d %H:%M'):
|
||||
return value.strftime(format) if value else ''
|
||||
```
|
||||
|
||||
## Development Workflow
|
||||
|
||||
### Getting Started
|
||||
1. Clone the repository
|
||||
2. Create virtual environment: `python -m venv venv`
|
||||
3. Activate environment: `source venv/bin/activate`
|
||||
4. Install dependencies: `pip install -r requirements/development.txt`
|
||||
5. Set environment variables
|
||||
6. Initialize database: `flask db upgrade`
|
||||
7. Run development server: `flask run`
|
||||
|
||||
### Development Process
|
||||
1. Create feature branch from main
|
||||
2. Implement changes with tests
|
||||
3. Run test suite: `pytest`
|
||||
4. Check code quality: `flake8`, `black`
|
||||
5. Create pull request for review
|
||||
6. Deploy after approval
|
||||
|
||||
### Code Quality Tools
|
||||
- **Black** - Code formatting
|
||||
- **isort** - Import sorting
|
||||
- **flake8** - Linting
|
||||
- **mypy** - Type checking
|
||||
- **pytest** - Testing framework
|
||||
- **coverage** - Test coverage
|
||||
@@ -0,0 +1,360 @@
|
||||
# Ruby Model Generator
|
||||
|
||||
Create Ruby classes and models with proper structure and best practices.
|
||||
|
||||
## Purpose
|
||||
|
||||
This command helps you quickly create Ruby model classes with proper initialization, attributes, and methods following Ruby conventions.
|
||||
|
||||
## Usage
|
||||
|
||||
```
|
||||
/model
|
||||
```
|
||||
|
||||
## What this command does
|
||||
|
||||
1. **Creates model classes** with proper initialization methods
|
||||
2. **Adds attribute accessors** and instance variables
|
||||
3. **Includes validation methods** and error handling
|
||||
4. **Follows Ruby conventions** and best practices
|
||||
5. **Generates related test files** when applicable
|
||||
|
||||
## Example Output
|
||||
|
||||
```ruby
|
||||
# app/models/user.rb
|
||||
class User
|
||||
attr_accessor :name, :email, :age
|
||||
attr_reader :id, :created_at
|
||||
|
||||
def initialize(attributes = {})
|
||||
@id = attributes[:id] || generate_id
|
||||
@name = attributes[:name]
|
||||
@email = attributes[:email]
|
||||
@age = attributes[:age]
|
||||
@created_at = Time.now
|
||||
@errors = []
|
||||
end
|
||||
|
||||
def valid?
|
||||
@errors.clear
|
||||
validate_presence_of_name
|
||||
validate_email_format
|
||||
validate_age_range
|
||||
@errors.empty?
|
||||
end
|
||||
|
||||
def errors
|
||||
@errors.dup
|
||||
end
|
||||
|
||||
def save
|
||||
return false unless valid?
|
||||
|
||||
# Persist logic here
|
||||
true
|
||||
end
|
||||
|
||||
def to_h
|
||||
{
|
||||
id: @id,
|
||||
name: @name,
|
||||
email: @email,
|
||||
age: @age,
|
||||
created_at: @created_at
|
||||
}
|
||||
end
|
||||
|
||||
def to_json(*args)
|
||||
JSON.generate(to_h, *args)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def validate_presence_of_name
|
||||
if @name.nil? || @name.strip.empty?
|
||||
@errors << "Name cannot be blank"
|
||||
end
|
||||
end
|
||||
|
||||
def validate_email_format
|
||||
if @email && !@email.match?(/\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i)
|
||||
@errors << "Email format is invalid"
|
||||
end
|
||||
end
|
||||
|
||||
def validate_age_range
|
||||
if @age && (@age < 0 || @age > 150)
|
||||
@errors << "Age must be between 0 and 150"
|
||||
end
|
||||
end
|
||||
|
||||
def generate_id
|
||||
SecureRandom.uuid
|
||||
end
|
||||
end
|
||||
|
||||
# Example with inheritance
|
||||
class AdminUser < User
|
||||
attr_accessor :permissions
|
||||
|
||||
def initialize(attributes = {})
|
||||
super
|
||||
@permissions = attributes[:permissions] || []
|
||||
end
|
||||
|
||||
def admin?
|
||||
true
|
||||
end
|
||||
|
||||
def has_permission?(permission)
|
||||
@permissions.include?(permission)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def validate_permissions
|
||||
unless @permissions.is_a?(Array)
|
||||
@errors << "Permissions must be an array"
|
||||
end
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
## Advanced Features
|
||||
|
||||
### Module Inclusion
|
||||
```ruby
|
||||
module Timestamps
|
||||
def self.included(base)
|
||||
base.extend(ClassMethods)
|
||||
end
|
||||
|
||||
module ClassMethods
|
||||
def with_timestamps
|
||||
attr_reader :created_at, :updated_at
|
||||
|
||||
define_method :initialize do |*args|
|
||||
super(*args)
|
||||
@created_at ||= Time.now
|
||||
@updated_at = @created_at
|
||||
end
|
||||
|
||||
define_method :touch do
|
||||
@updated_at = Time.now
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class Post
|
||||
include Timestamps
|
||||
with_timestamps
|
||||
|
||||
attr_accessor :title, :content
|
||||
|
||||
def initialize(attributes = {})
|
||||
@title = attributes[:title]
|
||||
@content = attributes[:content]
|
||||
super
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
### Class Methods and Scopes
|
||||
```ruby
|
||||
class User
|
||||
@@users = []
|
||||
|
||||
def self.all
|
||||
@@users.dup
|
||||
end
|
||||
|
||||
def self.find_by_email(email)
|
||||
@@users.find { |user| user.email == email }
|
||||
end
|
||||
|
||||
def self.where(conditions = {})
|
||||
@@users.select do |user|
|
||||
conditions.all? { |key, value| user.send(key) == value }
|
||||
end
|
||||
end
|
||||
|
||||
def self.create(attributes = {})
|
||||
user = new(attributes)
|
||||
if user.save
|
||||
@@users << user
|
||||
user
|
||||
else
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
def save
|
||||
return false unless valid?
|
||||
|
||||
unless @@users.include?(self)
|
||||
@@users << self
|
||||
end
|
||||
true
|
||||
end
|
||||
|
||||
def destroy
|
||||
@@users.delete(self)
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
## Testing Template
|
||||
|
||||
```ruby
|
||||
# spec/models/user_spec.rb
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe User do
|
||||
let(:valid_attributes) do
|
||||
{
|
||||
name: 'John Doe',
|
||||
email: 'john@example.com',
|
||||
age: 30
|
||||
}
|
||||
end
|
||||
|
||||
describe '#initialize' do
|
||||
it 'sets attributes correctly' do
|
||||
user = User.new(valid_attributes)
|
||||
|
||||
expect(user.name).to eq('John Doe')
|
||||
expect(user.email).to eq('john@example.com')
|
||||
expect(user.age).to eq(30)
|
||||
expect(user.id).not_to be_nil
|
||||
expect(user.created_at).to be_a(Time)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#valid?' do
|
||||
it 'returns true for valid attributes' do
|
||||
user = User.new(valid_attributes)
|
||||
expect(user).to be_valid
|
||||
end
|
||||
|
||||
it 'returns false when name is blank' do
|
||||
user = User.new(valid_attributes.merge(name: ''))
|
||||
expect(user).not_to be_valid
|
||||
expect(user.errors).to include('Name cannot be blank')
|
||||
end
|
||||
|
||||
it 'returns false for invalid email format' do
|
||||
user = User.new(valid_attributes.merge(email: 'invalid-email'))
|
||||
expect(user).not_to be_valid
|
||||
expect(user.errors).to include('Email format is invalid')
|
||||
end
|
||||
|
||||
it 'returns false for invalid age' do
|
||||
user = User.new(valid_attributes.merge(age: -5))
|
||||
expect(user).not_to be_valid
|
||||
expect(user.errors).to include('Age must be between 0 and 150')
|
||||
end
|
||||
end
|
||||
|
||||
describe '#save' do
|
||||
it 'saves valid user' do
|
||||
user = User.new(valid_attributes)
|
||||
expect(user.save).to be true
|
||||
end
|
||||
|
||||
it 'does not save invalid user' do
|
||||
user = User.new(name: '')
|
||||
expect(user.save).to be false
|
||||
end
|
||||
end
|
||||
|
||||
describe '#to_h' do
|
||||
it 'returns hash representation' do
|
||||
user = User.new(valid_attributes)
|
||||
hash = user.to_h
|
||||
|
||||
expect(hash).to include(
|
||||
name: 'John Doe',
|
||||
email: 'john@example.com',
|
||||
age: 30
|
||||
)
|
||||
expect(hash[:id]).not_to be_nil
|
||||
expect(hash[:created_at]).to be_a(Time)
|
||||
end
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
## Best Practices Included
|
||||
|
||||
- **Proper initialization** with hash parameters
|
||||
- **Attribute accessors** for public attributes
|
||||
- **Validation methods** with error collection
|
||||
- **JSON serialization** support
|
||||
- **Class and instance methods** separation
|
||||
- **Error handling** and reporting
|
||||
- **Ruby naming conventions** (snake_case)
|
||||
- **Encapsulation** with private methods
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### Value Objects
|
||||
```ruby
|
||||
class Money
|
||||
include Comparable
|
||||
|
||||
attr_reader :amount, :currency
|
||||
|
||||
def initialize(amount, currency = 'USD')
|
||||
@amount = amount.to_f
|
||||
@currency = currency.to_s.upcase
|
||||
end
|
||||
|
||||
def +(other)
|
||||
raise ArgumentError, "Currency mismatch" unless currency == other.currency
|
||||
Money.new(amount + other.amount, currency)
|
||||
end
|
||||
|
||||
def <=>(other)
|
||||
raise ArgumentError, "Currency mismatch" unless currency == other.currency
|
||||
amount <=> other.amount
|
||||
end
|
||||
|
||||
def to_s
|
||||
"#{currency} #{format('%.2f', amount)}"
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
### Service Objects
|
||||
```ruby
|
||||
class UserRegistrationService
|
||||
attr_reader :user, :errors
|
||||
|
||||
def initialize(user_params)
|
||||
@user_params = user_params
|
||||
@errors = []
|
||||
end
|
||||
|
||||
def call
|
||||
@user = User.new(@user_params)
|
||||
|
||||
if @user.valid?
|
||||
@user.save
|
||||
send_welcome_email
|
||||
true
|
||||
else
|
||||
@errors = @user.errors
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def send_welcome_email
|
||||
# Email sending logic
|
||||
end
|
||||
end
|
||||
```
|
||||
@@ -0,0 +1,480 @@
|
||||
# Ruby Test Generator
|
||||
|
||||
Create comprehensive test files with RSpec or Minitest following Ruby testing best practices.
|
||||
|
||||
## Purpose
|
||||
|
||||
This command helps you quickly create test files with proper structure, examples, and testing patterns for Ruby applications.
|
||||
|
||||
## Usage
|
||||
|
||||
```
|
||||
/test
|
||||
```
|
||||
|
||||
## What this command does
|
||||
|
||||
1. **Creates test files** with proper structure for RSpec or Minitest
|
||||
2. **Includes test examples** for common scenarios
|
||||
3. **Sets up test helpers** and support files
|
||||
4. **Follows testing conventions** and best practices
|
||||
5. **Generates factory/fixture data** when needed
|
||||
|
||||
## RSpec Example Output
|
||||
|
||||
```ruby
|
||||
# spec/models/user_spec.rb
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe User do
|
||||
let(:user) { build(:user) }
|
||||
let(:valid_attributes) do
|
||||
{
|
||||
name: 'John Doe',
|
||||
email: 'john@example.com',
|
||||
age: 30
|
||||
}
|
||||
end
|
||||
|
||||
describe 'validations' do
|
||||
it { should validate_presence_of(:name) }
|
||||
it { should validate_presence_of(:email) }
|
||||
it { should validate_uniqueness_of(:email) }
|
||||
it { should validate_numericality_of(:age).is_greater_than(0) }
|
||||
|
||||
context 'when email format is invalid' do
|
||||
let(:user) { build(:user, email: 'invalid-email') }
|
||||
|
||||
it 'is not valid' do
|
||||
expect(user).not_to be_valid
|
||||
expect(user.errors[:email]).to include('is invalid')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'associations' do
|
||||
it { should have_many(:posts) }
|
||||
it { should have_many(:comments) }
|
||||
end
|
||||
|
||||
describe 'callbacks' do
|
||||
describe 'before_save' do
|
||||
it 'normalizes email' do
|
||||
user = create(:user, email: 'JOHN@EXAMPLE.COM')
|
||||
expect(user.email).to eq('john@example.com')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'instance methods' do
|
||||
describe '#full_name' do
|
||||
let(:user) { build(:user, first_name: 'John', last_name: 'Doe') }
|
||||
|
||||
it 'returns the full name' do
|
||||
expect(user.full_name).to eq('John Doe')
|
||||
end
|
||||
|
||||
context 'when last_name is missing' do
|
||||
let(:user) { build(:user, first_name: 'John', last_name: nil) }
|
||||
|
||||
it 'returns only first_name' do
|
||||
expect(user.full_name).to eq('John')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#active?' do
|
||||
context 'when user is active' do
|
||||
let(:user) { build(:user, status: 'active') }
|
||||
|
||||
it 'returns true' do
|
||||
expect(user.active?).to be true
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user is inactive' do
|
||||
let(:user) { build(:user, status: 'inactive') }
|
||||
|
||||
it 'returns false' do
|
||||
expect(user.active?).to be false
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'class methods' do
|
||||
describe '.active' do
|
||||
let!(:active_user) { create(:user, status: 'active') }
|
||||
let!(:inactive_user) { create(:user, status: 'inactive') }
|
||||
|
||||
it 'returns only active users' do
|
||||
expect(User.active).to include(active_user)
|
||||
expect(User.active).not_to include(inactive_user)
|
||||
end
|
||||
end
|
||||
|
||||
describe '.find_by_email' do
|
||||
let!(:user) { create(:user, email: 'test@example.com') }
|
||||
|
||||
it 'finds user by email' do
|
||||
found_user = User.find_by_email('test@example.com')
|
||||
expect(found_user).to eq(user)
|
||||
end
|
||||
|
||||
it 'returns nil when user not found' do
|
||||
found_user = User.find_by_email('nonexistent@example.com')
|
||||
expect(found_user).to be_nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'scopes' do
|
||||
describe '.recent' do
|
||||
let!(:old_user) { create(:user, created_at: 1.year.ago) }
|
||||
let!(:recent_user) { create(:user, created_at: 1.day.ago) }
|
||||
|
||||
it 'returns users created in the last 30 days' do
|
||||
expect(User.recent).to include(recent_user)
|
||||
expect(User.recent).not_to include(old_user)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
## Minitest Example Output
|
||||
|
||||
```ruby
|
||||
# test/models/user_test.rb
|
||||
require 'test_helper'
|
||||
|
||||
class UserTest < ActiveSupport::TestCase
|
||||
def setup
|
||||
@user = users(:john)
|
||||
@valid_attributes = {
|
||||
name: 'Jane Doe',
|
||||
email: 'jane@example.com',
|
||||
age: 25
|
||||
}
|
||||
end
|
||||
|
||||
# Validation tests
|
||||
test 'should not save user without name' do
|
||||
user = User.new(@valid_attributes.except(:name))
|
||||
assert_not user.save
|
||||
assert_includes user.errors[:name], "can't be blank"
|
||||
end
|
||||
|
||||
test 'should not save user without email' do
|
||||
user = User.new(@valid_attributes.except(:email))
|
||||
assert_not user.save
|
||||
assert_includes user.errors[:email], "can't be blank"
|
||||
end
|
||||
|
||||
test 'should not save user with invalid email' do
|
||||
user = User.new(@valid_attributes.merge(email: 'invalid-email'))
|
||||
assert_not user.save
|
||||
assert_includes user.errors[:email], 'is invalid'
|
||||
end
|
||||
|
||||
test 'should not save user with duplicate email' do
|
||||
user1 = User.create!(@valid_attributes)
|
||||
user2 = User.new(@valid_attributes)
|
||||
assert_not user2.save
|
||||
assert_includes user2.errors[:email], 'has already been taken'
|
||||
end
|
||||
|
||||
test 'should save user with valid attributes' do
|
||||
user = User.new(@valid_attributes)
|
||||
assert user.save
|
||||
end
|
||||
|
||||
# Association tests
|
||||
test 'should have many posts' do
|
||||
assert_respond_to @user, :posts
|
||||
assert_kind_of ActiveRecord::Associations::CollectionProxy, @user.posts
|
||||
end
|
||||
|
||||
test 'should have many comments' do
|
||||
assert_respond_to @user, :comments
|
||||
end
|
||||
|
||||
# Instance method tests
|
||||
test 'full_name should return first and last name' do
|
||||
@user.first_name = 'John'
|
||||
@user.last_name = 'Doe'
|
||||
assert_equal 'John Doe', @user.full_name
|
||||
end
|
||||
|
||||
test 'full_name should return first name only when last name is missing' do
|
||||
@user.first_name = 'John'
|
||||
@user.last_name = nil
|
||||
assert_equal 'John', @user.full_name
|
||||
end
|
||||
|
||||
test 'active? should return true for active users' do
|
||||
@user.status = 'active'
|
||||
assert @user.active?
|
||||
end
|
||||
|
||||
test 'active? should return false for inactive users' do
|
||||
@user.status = 'inactive'
|
||||
assert_not @user.active?
|
||||
end
|
||||
|
||||
# Class method tests
|
||||
test 'active scope should return only active users' do
|
||||
active_user = User.create!(@valid_attributes.merge(status: 'active'))
|
||||
inactive_user = User.create!(@valid_attributes.merge(
|
||||
email: 'inactive@example.com',
|
||||
status: 'inactive'
|
||||
))
|
||||
|
||||
active_users = User.active
|
||||
assert_includes active_users, active_user
|
||||
assert_not_includes active_users, inactive_user
|
||||
end
|
||||
|
||||
test 'find_by_email should find user by email' do
|
||||
user = User.create!(@valid_attributes)
|
||||
found_user = User.find_by_email(@valid_attributes[:email])
|
||||
assert_equal user, found_user
|
||||
end
|
||||
|
||||
test 'find_by_email should return nil when user not found' do
|
||||
found_user = User.find_by_email('nonexistent@example.com')
|
||||
assert_nil found_user
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
## Controller Test Example
|
||||
|
||||
```ruby
|
||||
# spec/controllers/users_controller_spec.rb
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe UsersController, type: :controller do
|
||||
let(:user) { create(:user) }
|
||||
let(:valid_attributes) { attributes_for(:user) }
|
||||
let(:invalid_attributes) { { name: '', email: 'invalid' } }
|
||||
|
||||
describe 'GET #index' do
|
||||
it 'returns a success response' do
|
||||
get :index
|
||||
expect(response).to be_successful
|
||||
end
|
||||
|
||||
it 'assigns @users' do
|
||||
user1 = create(:user)
|
||||
user2 = create(:user)
|
||||
get :index
|
||||
expect(assigns(:users)).to match_array([user1, user2])
|
||||
end
|
||||
end
|
||||
|
||||
describe 'GET #show' do
|
||||
it 'returns a success response' do
|
||||
get :show, params: { id: user.to_param }
|
||||
expect(response).to be_successful
|
||||
end
|
||||
|
||||
it 'assigns the requested user' do
|
||||
get :show, params: { id: user.to_param }
|
||||
expect(assigns(:user)).to eq(user)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'POST #create' do
|
||||
context 'with valid parameters' do
|
||||
it 'creates a new User' do
|
||||
expect {
|
||||
post :create, params: { user: valid_attributes }
|
||||
}.to change(User, :count).by(1)
|
||||
end
|
||||
|
||||
it 'redirects to the created user' do
|
||||
post :create, params: { user: valid_attributes }
|
||||
expect(response).to redirect_to(User.last)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with invalid parameters' do
|
||||
it 'does not create a new User' do
|
||||
expect {
|
||||
post :create, params: { user: invalid_attributes }
|
||||
}.to change(User, :count).by(0)
|
||||
end
|
||||
|
||||
it 'renders the new template' do
|
||||
post :create, params: { user: invalid_attributes }
|
||||
expect(response).to render_template(:new)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'PUT #update' do
|
||||
context 'with valid parameters' do
|
||||
let(:new_attributes) { { name: 'Updated Name' } }
|
||||
|
||||
it 'updates the requested user' do
|
||||
put :update, params: { id: user.to_param, user: new_attributes }
|
||||
user.reload
|
||||
expect(user.name).to eq('Updated Name')
|
||||
end
|
||||
|
||||
it 'redirects to the user' do
|
||||
put :update, params: { id: user.to_param, user: new_attributes }
|
||||
expect(response).to redirect_to(user)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with invalid parameters' do
|
||||
it 'renders the edit template' do
|
||||
put :update, params: { id: user.to_param, user: invalid_attributes }
|
||||
expect(response).to render_template(:edit)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'DELETE #destroy' do
|
||||
it 'destroys the requested user' do
|
||||
user # Create user
|
||||
expect {
|
||||
delete :destroy, params: { id: user.to_param }
|
||||
}.to change(User, :count).by(-1)
|
||||
end
|
||||
|
||||
it 'redirects to the users list' do
|
||||
delete :destroy, params: { id: user.to_param }
|
||||
expect(response).to redirect_to(users_url)
|
||||
end
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
## Factory Configuration
|
||||
|
||||
```ruby
|
||||
# spec/factories/users.rb
|
||||
FactoryBot.define do
|
||||
factory :user do
|
||||
sequence(:name) { |n| "User #{n}" }
|
||||
sequence(:email) { |n| "user#{n}@example.com" }
|
||||
age { rand(18..80) }
|
||||
status { 'active' }
|
||||
|
||||
trait :inactive do
|
||||
status { 'inactive' }
|
||||
end
|
||||
|
||||
trait :admin do
|
||||
admin { true }
|
||||
end
|
||||
|
||||
trait :with_posts do
|
||||
after(:create) do |user|
|
||||
create_list(:post, 3, user: user)
|
||||
end
|
||||
end
|
||||
|
||||
factory :admin_user, traits: [:admin]
|
||||
factory :inactive_user, traits: [:inactive]
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
## Test Helper Configuration
|
||||
|
||||
```ruby
|
||||
# spec/spec_helper.rb
|
||||
require 'simplecov'
|
||||
SimpleCov.start 'rails' do
|
||||
add_filter '/spec/'
|
||||
add_filter '/config/'
|
||||
add_filter '/vendor/'
|
||||
|
||||
add_group 'Controllers', 'app/controllers'
|
||||
add_group 'Models', 'app/models'
|
||||
add_group 'Services', 'app/services'
|
||||
add_group 'Libraries', 'lib'
|
||||
end
|
||||
|
||||
RSpec.configure do |config|
|
||||
config.expect_with :rspec do |expectations|
|
||||
expectations.include_chain_clauses_in_custom_matcher_descriptions = true
|
||||
end
|
||||
|
||||
config.mock_with :rspec do |mocks|
|
||||
mocks.verify_partial_doubles = true
|
||||
end
|
||||
|
||||
config.shared_context_metadata_behavior = :apply_to_host_groups
|
||||
config.filter_run_when_matching :focus
|
||||
config.example_status_persistence_file_path = 'spec/examples.txt'
|
||||
config.disable_monkey_patching!
|
||||
config.warnings = true
|
||||
|
||||
if config.files_to_run.one?
|
||||
config.default_formatter = 'doc'
|
||||
end
|
||||
|
||||
config.profile_examples = 10
|
||||
config.order = :random
|
||||
Kernel.srand config.seed
|
||||
end
|
||||
```
|
||||
|
||||
## Integration Test Example
|
||||
|
||||
```ruby
|
||||
# spec/requests/api/users_spec.rb
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe 'API::Users', type: :request do
|
||||
let(:user) { create(:user) }
|
||||
let(:valid_headers) {
|
||||
{ 'Authorization' => "Bearer #{user.auth_token}" }
|
||||
}
|
||||
|
||||
describe 'GET /api/users' do
|
||||
it 'returns users' do
|
||||
create_list(:user, 3)
|
||||
|
||||
get '/api/users', headers: valid_headers
|
||||
|
||||
expect(response).to have_http_status(:ok)
|
||||
expect(JSON.parse(response.body)['users'].size).to eq(3)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'POST /api/users' do
|
||||
let(:valid_params) do
|
||||
{
|
||||
user: {
|
||||
name: 'New User',
|
||||
email: 'new@example.com'
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
it 'creates a new user' do
|
||||
post '/api/users', params: valid_params, headers: valid_headers
|
||||
|
||||
expect(response).to have_http_status(:created)
|
||||
expect(JSON.parse(response.body)['user']['name']).to eq('New User')
|
||||
end
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
## Best Practices Included
|
||||
|
||||
- **Descriptive test names** that explain expected behavior
|
||||
- **Proper test organization** with contexts and describes
|
||||
- **Factory usage** for test data generation
|
||||
- **Mocking and stubbing** for external dependencies
|
||||
- **Coverage configuration** with SimpleCov
|
||||
- **Test helpers** and shared examples
|
||||
- **Request/Integration tests** for API endpoints
|
||||
- **Feature tests** for user workflows
|
||||
@@ -0,0 +1,146 @@
|
||||
{
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"Bash",
|
||||
"Edit",
|
||||
"MultiEdit",
|
||||
"Write",
|
||||
"Bash(ruby:*)",
|
||||
"Bash(bundle:*)",
|
||||
"Bash(rails:*)",
|
||||
"Bash(rake:*)",
|
||||
"Bash(rspec:*)",
|
||||
"Bash(rubocop:*)",
|
||||
"Bash(brakeman:*)",
|
||||
"Bash(irb:*)",
|
||||
"Bash(pry:*)",
|
||||
"Bash(gem:*)",
|
||||
"Bash(rbenv:*)",
|
||||
"Bash(rvm:*)",
|
||||
"Bash(kamal:*)",
|
||||
"Bash(git:*)"
|
||||
],
|
||||
"deny": [
|
||||
"Bash(curl:*)",
|
||||
"Bash(wget:*)",
|
||||
"Bash(rm -rf:*)"
|
||||
],
|
||||
"defaultMode": "allowEdits"
|
||||
},
|
||||
"env": {
|
||||
"BASH_DEFAULT_TIMEOUT_MS": "60000",
|
||||
"BASH_MAX_OUTPUT_LENGTH": "20000",
|
||||
"CLAUDE_BASH_MAINTAIN_PROJECT_WORKING_DIR": "1",
|
||||
"BUNDLE_PATH": "vendor/bundle",
|
||||
"BUNDLE_JOBS": "4"
|
||||
},
|
||||
"includeCoAuthoredBy": true,
|
||||
"cleanupPeriodDays": 30,
|
||||
"hooks": {
|
||||
"PreToolUse": [
|
||||
{
|
||||
"matcher": "Bash",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "jq -r '\"\\(.tool_input.command) - \\(.tool_input.description // \"No description\")\"' >> ~/.claude/bash-command-log.txt"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"matcher": "Write",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "FILE=$(echo $STDIN_JSON | jq -r '.tool_input.file_path // \"\"); CONTENT=$(echo $STDIN_JSON | jq -r '.tool_input.content // \"\"); if [[ \"$FILE\" =~ \\.rb$ ]] && echo \"$CONTENT\" | grep -q 'puts\\|p '; then echo 'Warning: puts/p statements should be replaced with proper logging' >&2; exit 2; fi"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"matcher": "Write",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "FILE=$(echo $STDIN_JSON | jq -r '.tool_input.file_path // \"\"'); if [[ \"$FILE\" == \"Gemfile\" ]] || [[ \"$FILE\" == \"Gemfile.lock\" ]] || [[ \"$FILE\" =~ gemspec$ ]]; then echo 'Checking for vulnerable gems...'; if command -v bundle >/dev/null 2>&1 && bundle show bundler-audit >/dev/null 2>&1; then bundle audit; elif command -v bundle >/dev/null 2>&1; then echo 'Run: bundle add bundler-audit --group development to enable security audits'; else echo 'Bundler not found for security audit'; fi; fi",
|
||||
"timeout": 60
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"PostToolUse": [
|
||||
{
|
||||
"matcher": "Write|Edit|MultiEdit",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "FILE=$(echo $STDIN_JSON | jq -r '.tool_input.file_path // \"\"'); if [[ \"$FILE\" =~ \\.rb$ ]]; then if command -v rubocop >/dev/null 2>&1; then rubocop -A \"$FILE\" 2>/dev/null || echo 'RuboCop auto-correction applied'; elif command -v bundle >/dev/null 2>&1 && bundle show rubocop >/dev/null 2>&1; then bundle exec rubocop -A \"$FILE\" 2>/dev/null || echo 'RuboCop auto-correction applied'; else echo 'RuboCop not available for auto-correction'; fi; fi",
|
||||
"timeout": 30
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"matcher": "Write|Edit|MultiEdit",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "FILE=$(echo $STDIN_JSON | jq -r '.tool_input.file_path // \"\"'); if [[ \"$FILE\" =~ \\.rb$ ]]; then RESULT=$(if command -v rubocop >/dev/null 2>&1; then rubocop \"$FILE\" 2>&1; elif command -v bundle >/dev/null 2>&1 && bundle show rubocop >/dev/null 2>&1; then bundle exec rubocop \"$FILE\" 2>&1; fi); if [ $? -ne 0 ] && [[ -n \"$RESULT\" ]]; then echo \"RuboCop linting issues found: $RESULT\" >&2; exit 2; fi; fi",
|
||||
"timeout": 30
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"matcher": "Write|Edit|MultiEdit",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "FILE=$(echo $STDIN_JSON | jq -r '.tool_input.file_path // \"\"'); if [[ \"$FILE\" =~ \\.rb$ && \"$FILE\" != *\"spec/\"* && \"$FILE\" != *\"_spec.rb\" && \"$FILE\" != *\"test/\"* && \"$FILE\" != *\"_test.rb\" ]]; then DIR=$(dirname \"$FILE\"); BASENAME=$(basename \"$FILE\" .rb); for TEST_FILE in \"spec/${BASENAME}_spec.rb\" \"test/${BASENAME}_test.rb\" \"spec/models/${BASENAME}_spec.rb\" \"spec/controllers/${BASENAME}_spec.rb\" \"spec/services/${BASENAME}_spec.rb\"; do if [ -f \"$TEST_FILE\" ]; then echo \"Running tests for $TEST_FILE...\"; if command -v rspec >/dev/null 2>&1; then rspec \"$TEST_FILE\"; elif command -v bundle >/dev/null 2>&1 && bundle show rspec >/dev/null 2>&1; then bundle exec rspec \"$TEST_FILE\"; elif command -v ruby >/dev/null 2>&1; then ruby -Itest \"$TEST_FILE\" 2>/dev/null || echo 'Test runner not available'; fi; break; fi; done; fi",
|
||||
"timeout": 60
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"Notification": [
|
||||
{
|
||||
"matcher": "",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "echo \"Claude Code notification: $(date)\" >> ~/.claude/notifications.log"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"Stop": [
|
||||
{
|
||||
"matcher": "",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "if [[ -f Gemfile || -f Rakefile ]] && [[ $(git status --porcelain | grep '\\.rb$') ]]; then echo 'Running RuboCop on changed Ruby files...'; if command -v rubocop >/dev/null 2>&1; then rubocop $(git diff --name-only --diff-filter=ACMR | grep '\\.rb$'); elif command -v bundle >/dev/null 2>&1 && bundle show rubocop >/dev/null 2>&1; then bundle exec rubocop $(git diff --name-only --diff-filter=ACMR | grep '\\.rb$'); else echo 'RuboCop not found for linting'; fi; fi",
|
||||
"timeout": 60
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"matcher": "",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "if [[ -f Gemfile || -f Rakefile ]] && [[ $(git status --porcelain | grep '\\.rb$') ]]; then echo 'Running security scan with Brakeman...'; if command -v brakeman >/dev/null 2>&1; then brakeman --quiet --no-pager || echo 'Brakeman scan completed with findings'; elif command -v bundle >/dev/null 2>&1 && bundle show brakeman >/dev/null 2>&1; then bundle exec brakeman --quiet --no-pager || echo 'Brakeman scan completed with findings'; else echo 'Brakeman not found for security scanning'; fi; fi",
|
||||
"timeout": 60
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"matcher": "",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "if [[ -f Gemfile ]]; then echo 'Checking for gem vulnerabilities...'; if command -v bundle >/dev/null 2>&1 && bundle show bundler-audit >/dev/null 2>&1; then bundle audit || echo 'Security audit completed with findings'; else echo 'bundler-audit not found. Run: bundle add bundler-audit --group development'; fi; fi",
|
||||
"timeout": 30
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
{
|
||||
"mcpServers": {
|
||||
"github": {
|
||||
"command": "npx",
|
||||
"args": ["-y", "@modelcontextprotocol/server-github"],
|
||||
"category": "Development Tools",
|
||||
"description": "GitHub integration for repository management and issue tracking",
|
||||
"complexity": "Medium",
|
||||
"enabled": false,
|
||||
"env": {
|
||||
"GITHUB_PERSONAL_ACCESS_TOKEN": ""
|
||||
}
|
||||
},
|
||||
"postgres": {
|
||||
"command": "npx",
|
||||
"args": ["-y", "@modelcontextprotocol/server-postgres"],
|
||||
"category": "Database",
|
||||
"description": "PostgreSQL database operations and query execution",
|
||||
"complexity": "Medium",
|
||||
"enabled": false,
|
||||
"env": {
|
||||
"POSTGRES_CONNECTION_STRING": ""
|
||||
}
|
||||
},
|
||||
"brave-search": {
|
||||
"command": "npx",
|
||||
"args": ["-y", "@modelcontextprotocol/server-brave-search"],
|
||||
"category": "Web Search",
|
||||
"description": "Web search capabilities using Brave Search API",
|
||||
"complexity": "Low",
|
||||
"enabled": false,
|
||||
"env": {
|
||||
"BRAVE_API_KEY": ""
|
||||
}
|
||||
},
|
||||
"ruby-docs": {
|
||||
"command": "ruby",
|
||||
"args": [
|
||||
"-e",
|
||||
"require 'json'; require 'net/http'; puts JSON.generate({tools: [{name: 'ruby_docs', description: 'Search Ruby documentation'}]})"
|
||||
],
|
||||
"category": "Documentation",
|
||||
"description": "Ruby language documentation and API reference",
|
||||
"complexity": "Low",
|
||||
"enabled": true
|
||||
},
|
||||
"rails-docs": {
|
||||
"command": "ruby",
|
||||
"args": [
|
||||
"-e",
|
||||
"require 'json'; require 'net/http'; puts JSON.generate({tools: [{name: 'rails_docs', description: 'Search Rails documentation and guides'}]})"
|
||||
],
|
||||
"category": "Documentation",
|
||||
"description": "Ruby on Rails framework documentation and guides",
|
||||
"complexity": "Low",
|
||||
"enabled": true
|
||||
},
|
||||
"rubygems": {
|
||||
"command": "ruby",
|
||||
"args": [
|
||||
"-e",
|
||||
"require 'json'; require 'net/http'; puts JSON.generate({tools: [{name: 'gem_search', description: 'Search and explore Ruby gems'}]})"
|
||||
],
|
||||
"category": "Package Management",
|
||||
"description": "Search and explore Ruby gems from RubyGems.org",
|
||||
"complexity": "Low",
|
||||
"enabled": true
|
||||
},
|
||||
"bundler": {
|
||||
"command": "bundle",
|
||||
"args": [
|
||||
"exec",
|
||||
"ruby",
|
||||
"-e",
|
||||
"require 'json'; puts JSON.generate({tools: [{name: 'bundle_audit', description: 'Security audit for gems'}]})"
|
||||
],
|
||||
"category": "Security",
|
||||
"description": "Bundler integration for dependency management and security auditing",
|
||||
"complexity": "Medium",
|
||||
"enabled": true
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,284 @@
|
||||
# CLAUDE.md
|
||||
|
||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||
|
||||
## Project Overview
|
||||
|
||||
This is a Ruby project optimized for modern Ruby development. The project uses industry-standard tools and follows best practices for scalable application development.
|
||||
|
||||
## Development Commands
|
||||
|
||||
### Environment Management
|
||||
- `ruby --version` - Check Ruby version
|
||||
- `rbenv versions` - List available Ruby versions (with rbenv)
|
||||
- `rbenv install 3.2.0` - Install specific Ruby version
|
||||
- `rbenv local 3.2.0` - Set local Ruby version
|
||||
- `rvm use 3.2.0` - Use specific Ruby version (with RVM)
|
||||
|
||||
### Package Management
|
||||
- `bundle install` - Install dependencies from Gemfile
|
||||
- `bundle update` - Update all gems to latest versions
|
||||
- `bundle update <gem_name>` - Update specific gem
|
||||
- `bundle exec <command>` - Run command with bundled gems
|
||||
- `bundle add <gem_name>` - Add new gem to Gemfile
|
||||
- `bundle remove <gem_name>` - Remove gem from Gemfile
|
||||
- `gem install <gem_name>` - Install gem globally
|
||||
- `gem list` - List installed gems
|
||||
|
||||
### Testing Commands
|
||||
- `rspec` - Run RSpec tests
|
||||
- `rspec spec/models` - Run specific test directory
|
||||
- `rspec spec/models/user_spec.rb` - Run specific test file
|
||||
- `rspec --format documentation` - Run tests with detailed output
|
||||
- `rspec --tag focus` - Run tests with specific tag
|
||||
- `ruby -Itest test/` - Run Minitest tests
|
||||
- `rake test` - Run test suite via Rake
|
||||
- `bundle exec rspec` - Run RSpec with bundled gems
|
||||
|
||||
### Code Quality Commands
|
||||
- `rubocop` - Run RuboCop linter
|
||||
- `rubocop -A` - Auto-correct RuboCop violations
|
||||
- `rubocop --only <cop_name>` - Run specific RuboCop cop
|
||||
- `brakeman` - Run security analysis
|
||||
- `reek` - Run code smell detector
|
||||
- `yard` - Generate documentation
|
||||
- `bundle audit` - Check for security vulnerabilities in gems
|
||||
|
||||
### Development Tools
|
||||
- `irb` - Interactive Ruby console
|
||||
- `pry` - Enhanced Ruby console (if installed)
|
||||
- `ruby -c <file.rb>` - Check Ruby syntax
|
||||
- `ruby -w <file.rb>` - Run with warnings enabled
|
||||
|
||||
## Technology Stack
|
||||
|
||||
### Core Technologies
|
||||
- **Ruby** - Primary programming language (3.2.0+)
|
||||
- **Bundler** - Dependency management
|
||||
- **RubyGems** - Package management system
|
||||
|
||||
### Common Frameworks
|
||||
- **Ruby on Rails** - Full-stack web framework
|
||||
- **Sinatra** - Lightweight web framework
|
||||
- **Hanami** - Modern web framework
|
||||
- **Grape** - REST API framework
|
||||
- **Roda** - Routing tree web framework
|
||||
|
||||
### Testing Frameworks
|
||||
- **RSpec** - Behavior-driven development testing framework
|
||||
- **Minitest** - Built-in testing framework
|
||||
- **FactoryBot** - Test data generation
|
||||
- **Capybara** - Acceptance testing for web applications
|
||||
- **VCR** - Record HTTP interactions for tests
|
||||
- **WebMock** - Mock HTTP requests
|
||||
|
||||
### Code Quality Tools
|
||||
- **RuboCop** - Ruby static code analyzer and formatter
|
||||
- **Brakeman** - Static analysis security vulnerability scanner
|
||||
- **Reek** - Code smell detector
|
||||
- **SimpleCov** - Code coverage analysis
|
||||
- **YARD** - Documentation generation tool
|
||||
|
||||
### Popular Gems
|
||||
- **Puma** - Web server
|
||||
- **Sidekiq** - Background job processing
|
||||
- **Redis** - In-memory data structure store
|
||||
- **PostgreSQL/MySQL** - Database adapters
|
||||
- **Devise** - Authentication solution (pre-Rails 8)
|
||||
- **Pundit** - Authorization system
|
||||
|
||||
## Project Structure Guidelines
|
||||
|
||||
### File Organization
|
||||
```
|
||||
app/
|
||||
├── models/ # Business logic and data models
|
||||
├── controllers/ # Web controllers (Rails)
|
||||
├── views/ # Templates and presentation
|
||||
├── helpers/ # View helpers
|
||||
├── services/ # Business logic services
|
||||
├── workers/ # Background job workers
|
||||
└── lib/ # Custom libraries
|
||||
config/
|
||||
├── application.rb # Application configuration
|
||||
├── routes.rb # URL routing (Rails)
|
||||
├── database.yml # Database configuration
|
||||
└── environments/ # Environment-specific configs
|
||||
spec/ or test/
|
||||
├── models/ # Model tests
|
||||
├── controllers/ # Controller tests
|
||||
├── features/ # Feature/integration tests
|
||||
├── support/ # Test support files
|
||||
└── factories/ # Test data factories
|
||||
lib/
|
||||
├── tasks/ # Rake tasks
|
||||
└── custom_modules/ # Custom Ruby modules
|
||||
Gemfile # Gem dependencies
|
||||
Gemfile.lock # Locked gem versions
|
||||
Rakefile # Rake task definitions
|
||||
```
|
||||
|
||||
### Naming Conventions
|
||||
- **Files/Modules**: Use snake_case (`user_profile.rb`)
|
||||
- **Classes**: Use PascalCase (`UserProfile`)
|
||||
- **Methods/Variables**: Use snake_case (`get_user_data`)
|
||||
- **Constants**: Use SCREAMING_SNAKE_CASE (`API_BASE_URL`)
|
||||
- **Private methods**: Prefix with underscore or use `private` keyword
|
||||
|
||||
## Ruby Guidelines
|
||||
|
||||
### Code Style
|
||||
- Follow the Ruby Style Guide
|
||||
- Use meaningful variable and method names
|
||||
- Keep methods focused and single-purpose
|
||||
- Use Ruby idioms and conventions
|
||||
- Prefer explicit over implicit when it improves clarity
|
||||
- Use proper indentation (2 spaces)
|
||||
|
||||
### Best Practices
|
||||
- Use `bundle exec` for running commands with specific gem versions
|
||||
- Write tests for all public methods
|
||||
- Use descriptive commit messages
|
||||
- Keep Gemfile organized and commented
|
||||
- Use environment variables for configuration
|
||||
- Handle exceptions appropriately
|
||||
- Follow DRY (Don't Repeat Yourself) principles
|
||||
|
||||
## Testing Standards
|
||||
|
||||
### Test Structure
|
||||
- Organize tests to mirror source code structure
|
||||
- Use descriptive test names that explain the behavior
|
||||
- Follow AAA pattern (Arrange, Act, Assert)
|
||||
- Use factories for test data
|
||||
- Group related tests in context blocks (RSpec)
|
||||
|
||||
### Coverage Goals
|
||||
- Aim for 90%+ test coverage
|
||||
- Write unit tests for models and services
|
||||
- Use integration tests for controllers and features
|
||||
- Mock external dependencies
|
||||
- Test error conditions and edge cases
|
||||
|
||||
### RSpec Configuration
|
||||
```ruby
|
||||
# spec/spec_helper.rb
|
||||
RSpec.configure do |config|
|
||||
config.expect_with :rspec do |expectations|
|
||||
expectations.include_chain_clauses_in_custom_matcher_descriptions = true
|
||||
end
|
||||
|
||||
config.mock_with :rspec do |mocks|
|
||||
mocks.verify_partial_doubles = true
|
||||
end
|
||||
|
||||
config.shared_context_metadata_behavior = :apply_to_host_groups
|
||||
config.filter_run_when_matching :focus
|
||||
config.example_status_persistence_file_path = "spec/examples.txt"
|
||||
config.disable_monkey_patching!
|
||||
config.warnings = true
|
||||
|
||||
if config.files_to_run.one?
|
||||
config.default_formatter = "doc"
|
||||
end
|
||||
|
||||
config.profile_examples = 10
|
||||
config.order = :random
|
||||
Kernel.srand config.seed
|
||||
end
|
||||
```
|
||||
|
||||
## Bundler Configuration
|
||||
|
||||
### Gemfile Best Practices
|
||||
```ruby
|
||||
# Gemfile
|
||||
source 'https://rubygems.org'
|
||||
git_source(:github) { |repo| "https://github.com/#{repo}.git" }
|
||||
|
||||
ruby '3.2.0'
|
||||
|
||||
# Core gems
|
||||
gem 'rails', '~> 8.0.0'
|
||||
gem 'pg', '~> 1.1'
|
||||
gem 'puma', '~> 6.0'
|
||||
|
||||
# Development and test gems
|
||||
group :development, :test do
|
||||
gem 'debug', platforms: %i[ mri mingw x64_mingw ]
|
||||
gem 'rspec-rails'
|
||||
gem 'factory_bot_rails'
|
||||
end
|
||||
|
||||
group :development do
|
||||
gem 'rubocop', require: false
|
||||
gem 'brakeman', require: false
|
||||
gem 'web-console'
|
||||
end
|
||||
|
||||
group :test do
|
||||
gem 'capybara'
|
||||
gem 'selenium-webdriver'
|
||||
gem 'simplecov', require: false
|
||||
end
|
||||
```
|
||||
|
||||
### Bundle Configuration
|
||||
```bash
|
||||
# .bundle/config or config/bundle
|
||||
BUNDLE_PATH: "vendor/bundle"
|
||||
BUNDLE_JOBS: "4"
|
||||
BUNDLE_RETRY: "3"
|
||||
```
|
||||
|
||||
## Development Workflow
|
||||
|
||||
### Before Starting
|
||||
1. Check Ruby version compatibility
|
||||
2. Install dependencies with `bundle install`
|
||||
3. Set up database (if applicable)
|
||||
4. Run tests to ensure setup is correct
|
||||
|
||||
### During Development
|
||||
1. Write tests first (TDD approach)
|
||||
2. Run tests frequently: `bundle exec rspec`
|
||||
3. Use meaningful commit messages
|
||||
4. Run code quality checks regularly
|
||||
|
||||
### Before Committing
|
||||
1. Run full test suite: `bundle exec rspec`
|
||||
2. Run linter: `rubocop`
|
||||
3. Run security scanner: `brakeman`
|
||||
4. Check for vulnerabilities: `bundle audit`
|
||||
5. Ensure code coverage is maintained
|
||||
|
||||
## Security Guidelines
|
||||
|
||||
### Gem Security
|
||||
- Regularly update gems with `bundle update`
|
||||
- Use `bundle audit` to check for known vulnerabilities
|
||||
- Pin gem versions in Gemfile.lock
|
||||
- Review gem source code for suspicious packages
|
||||
|
||||
### Code Security
|
||||
- Validate input data
|
||||
- Use environment variables for sensitive configuration
|
||||
- Implement proper authentication and authorization
|
||||
- Sanitize data before database operations
|
||||
- Use HTTPS for production deployments
|
||||
- Follow OWASP guidelines for web security
|
||||
|
||||
## Performance Considerations
|
||||
|
||||
### Code Performance
|
||||
- Use proper indexing for database queries
|
||||
- Implement caching strategies
|
||||
- Profile code with tools like `ruby-prof`
|
||||
- Monitor memory usage
|
||||
- Use background jobs for heavy operations
|
||||
|
||||
### Gem Performance
|
||||
- Choose gems wisely based on performance metrics
|
||||
- Monitor gem overhead
|
||||
- Use lightweight alternatives when possible
|
||||
- Profile application with production-like data
|
||||
@@ -0,0 +1,490 @@
|
||||
# Rails 8 Native Authentication Generator
|
||||
|
||||
Generate Rails 8's built-in authentication system with modern security practices.
|
||||
|
||||
## Purpose
|
||||
|
||||
This command helps you implement Rails 8's new native authentication system, eliminating the need for external gems like Devise for basic authentication needs.
|
||||
|
||||
## Usage
|
||||
|
||||
```
|
||||
/authentication
|
||||
```
|
||||
|
||||
## What this command does
|
||||
|
||||
1. **Generates authentication models** with secure password handling
|
||||
2. **Creates authentication controllers** for login/logout/signup
|
||||
3. **Adds authentication views** with modern styling
|
||||
4. **Implements session management** with security best practices
|
||||
5. **Sets up authentication helpers** for controllers and views
|
||||
|
||||
## Rails 8 Authentication Generator
|
||||
|
||||
```bash
|
||||
# Generate authentication for User model
|
||||
bin/rails generate authentication User
|
||||
|
||||
# Or specify custom model name
|
||||
bin/rails generate authentication Account
|
||||
|
||||
# Generate with custom attributes
|
||||
bin/rails generate authentication User first_name:string last_name:string
|
||||
```
|
||||
|
||||
## Generated User Model
|
||||
|
||||
```ruby
|
||||
# app/models/user.rb
|
||||
class User < ApplicationRecord
|
||||
has_secure_password
|
||||
|
||||
validates :email, presence: true, uniqueness: true
|
||||
validates :password, length: { minimum: 8 }, if: -> { new_record? || !password.blank? }
|
||||
|
||||
normalizes :email, with: ->(email) { email.strip.downcase }
|
||||
|
||||
before_save :normalize_email
|
||||
|
||||
private
|
||||
|
||||
def normalize_email
|
||||
self.email = email.downcase.strip
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
## Authentication Controller
|
||||
|
||||
```ruby
|
||||
# app/controllers/authentication_controller.rb
|
||||
class AuthenticationController < ApplicationController
|
||||
skip_before_action :authenticate_user!, only: [:new, :create]
|
||||
|
||||
def new
|
||||
# Login page
|
||||
end
|
||||
|
||||
def create
|
||||
user = User.find_by(email: params[:email])
|
||||
|
||||
if user&.authenticate(params[:password])
|
||||
login(user)
|
||||
redirect_to root_path, notice: 'Logged in successfully'
|
||||
else
|
||||
flash.now[:alert] = 'Invalid email or password'
|
||||
render :new, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
def destroy
|
||||
logout
|
||||
redirect_to root_path, notice: 'Logged out successfully'
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def login(user)
|
||||
session[:user_id] = user.id
|
||||
@current_user = user
|
||||
end
|
||||
|
||||
def logout
|
||||
session[:user_id] = nil
|
||||
@current_user = nil
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
## Registration Controller
|
||||
|
||||
```ruby
|
||||
# app/controllers/registrations_controller.rb
|
||||
class RegistrationsController < ApplicationController
|
||||
skip_before_action :authenticate_user!
|
||||
|
||||
def new
|
||||
@user = User.new
|
||||
end
|
||||
|
||||
def create
|
||||
@user = User.new(user_params)
|
||||
|
||||
if @user.save
|
||||
login(@user)
|
||||
redirect_to root_path, notice: 'Account created successfully'
|
||||
else
|
||||
render :new, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def user_params
|
||||
params.require(:user).permit(:email, :password, :password_confirmation, :first_name, :last_name)
|
||||
end
|
||||
|
||||
def login(user)
|
||||
session[:user_id] = user.id
|
||||
@current_user = user
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
## Application Controller Updates
|
||||
|
||||
```ruby
|
||||
# app/controllers/application_controller.rb
|
||||
class ApplicationController < ActionController::Base
|
||||
protect_from_forgery with: :exception
|
||||
|
||||
before_action :authenticate_user!
|
||||
|
||||
private
|
||||
|
||||
def authenticate_user!
|
||||
redirect_to login_path, alert: 'Please log in to continue' unless current_user
|
||||
end
|
||||
|
||||
def current_user
|
||||
@current_user ||= User.find(session[:user_id]) if session[:user_id]
|
||||
end
|
||||
helper_method :current_user
|
||||
|
||||
def logged_in?
|
||||
!!current_user
|
||||
end
|
||||
helper_method :logged_in?
|
||||
|
||||
def require_login
|
||||
unless logged_in?
|
||||
flash[:alert] = 'You must be logged in to access this page'
|
||||
redirect_to login_path
|
||||
end
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
## Authentication Views
|
||||
|
||||
### Login Form
|
||||
```erb
|
||||
<!-- app/views/authentication/new.html.erb -->
|
||||
<div class="authentication-form">
|
||||
<h1>Log In</h1>
|
||||
|
||||
<%= form_with url: login_path, local: true, class: "auth-form" do |form| %>
|
||||
<div class="form-group">
|
||||
<%= form.label :email, "Email" %>
|
||||
<%= form.email_field :email, required: true, autofocus: true, class: "form-control" %>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<%= form.label :password, "Password" %>
|
||||
<%= form.password_field :password, required: true, class: "form-control" %>
|
||||
</div>
|
||||
|
||||
<div class="form-actions">
|
||||
<%= form.submit "Log In", class: "btn btn-primary" %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<div class="auth-links">
|
||||
<%= link_to "Don't have an account? Sign up", signup_path %>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
### Registration Form
|
||||
```erb
|
||||
<!-- app/views/registrations/new.html.erb -->
|
||||
<div class="authentication-form">
|
||||
<h1>Sign Up</h1>
|
||||
|
||||
<%= form_with model: @user, url: signup_path, local: true, class: "auth-form" do |form| %>
|
||||
<% if @user.errors.any? %>
|
||||
<div class="error-messages">
|
||||
<h3><%= pluralize(@user.errors.count, "error") %> prohibited this account from being saved:</h3>
|
||||
<ul>
|
||||
<% @user.errors.full_messages.each do |message| %>
|
||||
<li><%= message %></li>
|
||||
<% end %>
|
||||
</ul>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<div class="form-group">
|
||||
<%= form.label :first_name, "First Name" %>
|
||||
<%= form.text_field :first_name, class: "form-control" %>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<%= form.label :last_name, "Last Name" %>
|
||||
<%= form.text_field :last_name, class: "form-control" %>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<%= form.label :email, "Email" %>
|
||||
<%= form.email_field :email, required: true, class: "form-control" %>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<%= form.label :password, "Password" %>
|
||||
<%= form.password_field :password, required: true, minlength: 8, class: "form-control" %>
|
||||
<small class="form-text">Minimum 8 characters</small>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<%= form.label :password_confirmation, "Confirm Password" %>
|
||||
<%= form.password_field :password_confirmation, required: true, class: "form-control" %>
|
||||
</div>
|
||||
|
||||
<div class="form-actions">
|
||||
<%= form.submit "Sign Up", class: "btn btn-primary" %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<div class="auth-links">
|
||||
<%= link_to "Already have an account? Log in", login_path %>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
## Route Configuration
|
||||
|
||||
```ruby
|
||||
# config/routes.rb
|
||||
Rails.application.routes.draw do
|
||||
# Authentication routes
|
||||
get 'login', to: 'authentication#new'
|
||||
post 'login', to: 'authentication#create'
|
||||
delete 'logout', to: 'authentication#destroy'
|
||||
|
||||
# Registration routes
|
||||
get 'signup', to: 'registrations#new'
|
||||
post 'signup', to: 'registrations#create'
|
||||
|
||||
# User management routes
|
||||
resources :users, except: [:new, :create]
|
||||
|
||||
# Root route
|
||||
root 'dashboard#index'
|
||||
end
|
||||
```
|
||||
|
||||
## Migration Files
|
||||
|
||||
```ruby
|
||||
# db/migrate/xxx_create_users.rb
|
||||
class CreateUsers < ActiveRecord::Migration[8.0]
|
||||
def change
|
||||
create_table :users do |t|
|
||||
t.string :email, null: false
|
||||
t.string :password_digest, null: false
|
||||
t.string :first_name
|
||||
t.string :last_name
|
||||
|
||||
t.timestamps
|
||||
end
|
||||
|
||||
add_index :users, :email, unique: true
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
## Advanced Authentication Features
|
||||
|
||||
### Password Reset
|
||||
```ruby
|
||||
# app/controllers/password_resets_controller.rb
|
||||
class PasswordResetsController < ApplicationController
|
||||
skip_before_action :authenticate_user!
|
||||
|
||||
def new
|
||||
# Password reset request form
|
||||
end
|
||||
|
||||
def create
|
||||
@user = User.find_by(email: params[:email])
|
||||
if @user
|
||||
@user.send_password_reset
|
||||
redirect_to root_path, notice: 'Email sent with password reset instructions'
|
||||
else
|
||||
flash.now[:alert] = 'Email address not found'
|
||||
render :new
|
||||
end
|
||||
end
|
||||
|
||||
def edit
|
||||
@user = User.find_by(password_reset_token: params[:id])
|
||||
if @user.nil? || @user.password_reset_sent_at < 2.hours.ago
|
||||
redirect_to new_password_reset_path, alert: 'Password reset has expired'
|
||||
end
|
||||
end
|
||||
|
||||
def update
|
||||
@user = User.find_by(password_reset_token: params[:id])
|
||||
if @user && @user.password_reset_sent_at > 2.hours.ago
|
||||
if @user.update(password_params)
|
||||
@user.update_columns(password_reset_token: nil, password_reset_sent_at: nil)
|
||||
redirect_to root_path, notice: 'Password has been reset'
|
||||
else
|
||||
render :edit
|
||||
end
|
||||
else
|
||||
redirect_to new_password_reset_path, alert: 'Password reset has expired'
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def password_params
|
||||
params.require(:user).permit(:password, :password_confirmation)
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
### User Model Extensions
|
||||
```ruby
|
||||
# app/models/user.rb (extended)
|
||||
class User < ApplicationRecord
|
||||
has_secure_password
|
||||
|
||||
validates :email, presence: true, uniqueness: true
|
||||
validates :password, length: { minimum: 8 }, if: -> { new_record? || !password.blank? }
|
||||
|
||||
normalizes :email, with: ->(email) { email.strip.downcase }
|
||||
|
||||
# Password reset functionality
|
||||
def send_password_reset
|
||||
generate_token(:password_reset_token)
|
||||
self.password_reset_sent_at = Time.zone.now
|
||||
save!
|
||||
UserMailer.password_reset(self).deliver_now
|
||||
end
|
||||
|
||||
def full_name
|
||||
"#{first_name} #{last_name}".strip
|
||||
end
|
||||
|
||||
def initials
|
||||
"#{first_name&.first}#{last_name&.first}".upcase
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def generate_token(column)
|
||||
begin
|
||||
self[column] = SecureRandom.urlsafe_base64
|
||||
end while User.exists?(column => self[column])
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
## Session Security Enhancements
|
||||
|
||||
```ruby
|
||||
# config/application.rb
|
||||
config.session_store :cookie_store, {
|
||||
key: '_myapp_session',
|
||||
secure: Rails.env.production?,
|
||||
httponly: true,
|
||||
same_site: :lax
|
||||
}
|
||||
|
||||
# config/environments/production.rb
|
||||
config.force_ssl = true
|
||||
config.session_store :cookie_store, {
|
||||
key: '_myapp_session',
|
||||
secure: true,
|
||||
httponly: true,
|
||||
same_site: :strict
|
||||
}
|
||||
```
|
||||
|
||||
## Authentication Helper Methods
|
||||
|
||||
```ruby
|
||||
# app/helpers/authentication_helper.rb
|
||||
module AuthenticationHelper
|
||||
def user_avatar(user, size: 40)
|
||||
if user.avatar.present?
|
||||
image_tag user.avatar, alt: user.full_name, class: "avatar", size: "#{size}x#{size}"
|
||||
else
|
||||
content_tag :div, user.initials, class: "avatar avatar-initials",
|
||||
style: "width: #{size}px; height: #{size}px; line-height: #{size}px;"
|
||||
end
|
||||
end
|
||||
|
||||
def current_user_menu
|
||||
if logged_in?
|
||||
render 'shared/user_menu'
|
||||
else
|
||||
render 'shared/guest_menu'
|
||||
end
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
## Testing Authentication
|
||||
|
||||
```ruby
|
||||
# spec/models/user_spec.rb
|
||||
RSpec.describe User, type: :model do
|
||||
describe 'validations' do
|
||||
it { should validate_presence_of(:email) }
|
||||
it { should validate_uniqueness_of(:email) }
|
||||
it { should validate_length_of(:password).is_at_least(8) }
|
||||
it { should have_secure_password }
|
||||
end
|
||||
|
||||
describe 'email normalization' do
|
||||
it 'normalizes email to lowercase' do
|
||||
user = User.create!(email: 'USER@EXAMPLE.COM', password: 'password123')
|
||||
expect(user.email).to eq('user@example.com')
|
||||
end
|
||||
end
|
||||
|
||||
describe '#full_name' do
|
||||
it 'returns combined first and last name' do
|
||||
user = User.new(first_name: 'John', last_name: 'Doe')
|
||||
expect(user.full_name).to eq('John Doe')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# spec/controllers/authentication_controller_spec.rb
|
||||
RSpec.describe AuthenticationController, type: :controller do
|
||||
describe 'POST #create' do
|
||||
let(:user) { create(:user, password: 'password123') }
|
||||
|
||||
context 'with valid credentials' do
|
||||
it 'logs in the user' do
|
||||
post :create, params: { email: user.email, password: 'password123' }
|
||||
expect(session[:user_id]).to eq(user.id)
|
||||
expect(response).to redirect_to(root_path)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with invalid credentials' do
|
||||
it 'does not log in the user' do
|
||||
post :create, params: { email: user.email, password: 'wrong' }
|
||||
expect(session[:user_id]).to be_nil
|
||||
expect(response).to render_template(:new)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
## Security Best Practices Included
|
||||
|
||||
- **Secure password hashing** with bcrypt
|
||||
- **Email normalization** to prevent duplicates
|
||||
- **CSRF protection** enabled by default
|
||||
- **Session security** with httponly and secure flags
|
||||
- **Password strength validation** (minimum 8 characters)
|
||||
- **Timing attack prevention** in authentication
|
||||
- **Password reset token expiration**
|
||||
- **SSL enforcement** in production
|
||||
@@ -0,0 +1,376 @@
|
||||
# Rails 8 Project Configuration
|
||||
|
||||
This file provides specific guidance for Ruby on Rails 8 web application development using Claude Code.
|
||||
|
||||
## Project Overview
|
||||
|
||||
This is a Ruby on Rails 8 web application project optimized for modern web development with the latest Rails features. The project follows Rails 8 conventions and leverages new capabilities like native authentication, Solid trifecta, and Kamal 2 deployment.
|
||||
|
||||
## Rails 8 Specific Development Commands
|
||||
|
||||
### Project Creation & Setup
|
||||
- `rails new myapp` - Create new Rails 8 application
|
||||
- `rails new myapp --skip-kamal` - Create app without Kamal deployment
|
||||
- `rails new myapp --database=postgresql` - Create app with PostgreSQL
|
||||
- `rails new myapp --css=tailwind` - Create app with Tailwind CSS
|
||||
- `rails new myapp --javascript=esbuild` - Create app with esbuild
|
||||
|
||||
### Server Management
|
||||
- `bin/rails server` or `bin/rails s` - Start development server
|
||||
- `bin/rails server -p 4000` - Start server on port 4000
|
||||
- `bin/rails server -e production` - Start in production mode
|
||||
- `bin/dev` - Start development server with asset compilation (if available)
|
||||
|
||||
### Database Management
|
||||
- `bin/rails db:create` - Create database
|
||||
- `bin/rails db:migrate` - Run pending migrations
|
||||
- `bin/rails db:rollback` - Rollback last migration
|
||||
- `bin/rails db:reset` - Drop, create, and migrate database
|
||||
- `bin/rails db:seed` - Run database seeds
|
||||
- `bin/rails db:setup` - Create, migrate, and seed database
|
||||
|
||||
### Generation Commands
|
||||
- `bin/rails generate model User name:string email:string` - Generate model
|
||||
- `bin/rails generate controller Users index show` - Generate controller
|
||||
- `bin/rails generate migration AddIndexToUsers email:index` - Generate migration
|
||||
- `bin/rails generate authentication` - Generate Rails 8 native authentication (NEW)
|
||||
- `bin/rails generate solid_queue:install` - Install Solid Queue (NEW)
|
||||
- `bin/rails generate solid_cache:install` - Install Solid Cache (NEW)
|
||||
|
||||
### Rails 8 Native Authentication
|
||||
- `bin/rails generate authentication User` - Generate authentication for User model
|
||||
- `bin/rails generate authentication:views` - Generate authentication views
|
||||
- `bin/rails generate authentication:controllers` - Generate authentication controllers
|
||||
|
||||
### Background Jobs (Solid Queue)
|
||||
- `bin/rails solid_queue:start` - Start Solid Queue worker
|
||||
- `bin/rails solid_queue:install` - Install Solid Queue configuration
|
||||
- `SampleJob.perform_later(user)` - Enqueue background job
|
||||
|
||||
### Caching (Solid Cache)
|
||||
- `Rails.cache.write("key", "value")` - Write to cache
|
||||
- `Rails.cache.read("key")` - Read from cache
|
||||
- `Rails.cache.delete("key")` - Delete from cache
|
||||
- `bin/rails solid_cache:clear` - Clear all cached data
|
||||
|
||||
### Asset Management (Propshaft)
|
||||
- `bin/rails assets:precompile` - Precompile assets for production
|
||||
- `bin/rails assets:clobber` - Remove compiled assets
|
||||
- Assets are automatically served in development
|
||||
|
||||
### Testing Commands
|
||||
- `bin/rails test` - Run all tests
|
||||
- `bin/rails test:models` - Run model tests
|
||||
- `bin/rails test:controllers` - Run controller tests
|
||||
- `bin/rails test:system` - Run system tests
|
||||
- `bin/rails test test/models/user_test.rb` - Run specific test file
|
||||
|
||||
### Console & Debugging
|
||||
- `bin/rails console` or `bin/rails c` - Start Rails console
|
||||
- `bin/rails console --sandbox` - Start console in sandbox mode
|
||||
- `bin/rails dbconsole` or `bin/rails db` - Start database console
|
||||
|
||||
### Code Quality & Security (Rails 8 Defaults)
|
||||
- `bin/rails rubocop` - Run RuboCop with Rails 8 Omakase config
|
||||
- `bin/rails brakeman` - Run security scan (included by default)
|
||||
- `bundle audit` - Check for vulnerable gems
|
||||
|
||||
## Deployment with Kamal 2 (Rails 8 Default)
|
||||
|
||||
### Kamal 2 Commands
|
||||
- `kamal setup` - Initial server setup
|
||||
- `kamal deploy` - Deploy application
|
||||
- `kamal redeploy` - Redeploy without setup
|
||||
- `kamal rollback` - Rollback to previous version
|
||||
- `kamal logs` - View application logs
|
||||
- `kamal logs --follow` - Follow logs in real-time
|
||||
- `kamal shell` - SSH into deployed container
|
||||
- `kamal details` - Show deployment details
|
||||
|
||||
### Kamal 2 Configuration
|
||||
Configuration is automatically generated in `config/deploy.yml`:
|
||||
|
||||
```yaml
|
||||
# config/deploy.yml
|
||||
service: myapp
|
||||
image: myapp
|
||||
servers:
|
||||
- 192.168.1.1
|
||||
registry:
|
||||
server: registry.digitalocean.com
|
||||
username: myusername
|
||||
|
||||
env:
|
||||
clear:
|
||||
RAILS_ENV: production
|
||||
secret:
|
||||
- RAILS_MASTER_KEY
|
||||
|
||||
builder:
|
||||
multiarch: false
|
||||
```
|
||||
|
||||
## Rails 8 Project Structure
|
||||
|
||||
```
|
||||
myapp/
|
||||
├── app/
|
||||
│ ├── models/ # ActiveRecord models
|
||||
│ ├── controllers/ # ActionController controllers
|
||||
│ ├── views/ # ERB/HAML templates
|
||||
│ ├── helpers/ # View helpers
|
||||
│ ├── mailers/ # ActionMailer mailers
|
||||
│ ├── jobs/ # ActiveJob jobs (Solid Queue)
|
||||
│ ├── assets/ # Application assets (Propshaft)
|
||||
│ └── javascript/ # JavaScript files
|
||||
├── config/
|
||||
│ ├── application.rb # Application configuration
|
||||
│ ├── routes.rb # URL routing
|
||||
│ ├── database.yml # Database configuration
|
||||
│ ├── deploy.yml # Kamal 2 deployment config (NEW)
|
||||
│ └── environments/ # Environment-specific configs
|
||||
├── db/
|
||||
│ ├── migrate/ # Database migrations
|
||||
│ └── seeds.rb # Database seeds
|
||||
├── test/ # Test files (default in Rails 8)
|
||||
│ ├── models/
|
||||
│ ├── controllers/
|
||||
│ ├── integration/
|
||||
│ └── system/
|
||||
├── bin/ # Binstubs
|
||||
├── config.ru # Rack configuration
|
||||
├── Gemfile # Gem dependencies
|
||||
├── Gemfile.lock # Locked gem versions
|
||||
└── Dockerfile # Docker configuration (if using containers)
|
||||
```
|
||||
|
||||
## Rails 8 New Features Integration
|
||||
|
||||
### 1. SQLite Production Enhancements
|
||||
Rails 8 makes SQLite production-ready:
|
||||
|
||||
```ruby
|
||||
# config/database.yml
|
||||
production:
|
||||
adapter: sqlite3
|
||||
database: storage/production.sqlite3
|
||||
pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
|
||||
timeout: 5000
|
||||
# New Rails 8 SQLite configurations
|
||||
pragmas:
|
||||
busy_timeout: 1000
|
||||
journal_mode: WAL
|
||||
synchronous: NORMAL
|
||||
foreign_keys: true
|
||||
```
|
||||
|
||||
### 2. Solid Trifecta Configuration
|
||||
|
||||
#### Solid Queue (Background Jobs)
|
||||
```ruby
|
||||
# config/application.rb
|
||||
config.active_job.queue_adapter = :solid_queue
|
||||
|
||||
# app/jobs/sample_job.rb
|
||||
class SampleJob < ApplicationJob
|
||||
queue_as :default
|
||||
|
||||
def perform(user)
|
||||
# Background job logic
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
#### Solid Cache (Caching)
|
||||
```ruby
|
||||
# config/application.rb
|
||||
config.cache_store = :solid_cache_store
|
||||
|
||||
# Usage in controllers/models
|
||||
Rails.cache.fetch("user_#{user.id}", expires_in: 1.hour) do
|
||||
expensive_calculation(user)
|
||||
end
|
||||
```
|
||||
|
||||
#### Solid Cable (WebSockets)
|
||||
```ruby
|
||||
# config/application.rb
|
||||
config.action_cable.adapter = :solid_cable
|
||||
|
||||
# app/channels/chat_channel.rb
|
||||
class ChatChannel < ApplicationCable::Channel
|
||||
def subscribed
|
||||
stream_from "chat_#{params[:room]}"
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
### 3. Native Authentication (Rails 8)
|
||||
```ruby
|
||||
# Generate authentication
|
||||
bin/rails generate authentication User
|
||||
|
||||
# app/models/user.rb (generated)
|
||||
class User < ApplicationRecord
|
||||
has_secure_password
|
||||
|
||||
validates :email, presence: true, uniqueness: true
|
||||
normalizes :email, with: ->(email) { email.strip.downcase }
|
||||
end
|
||||
|
||||
# app/controllers/application_controller.rb
|
||||
class ApplicationController < ActionController::Base
|
||||
before_action :authenticate_user!
|
||||
|
||||
private
|
||||
|
||||
def authenticate_user!
|
||||
redirect_to login_path unless current_user
|
||||
end
|
||||
|
||||
def current_user
|
||||
@current_user ||= User.find(session[:user_id]) if session[:user_id]
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
### 4. Propshaft Asset Pipeline
|
||||
```ruby
|
||||
# config/application.rb
|
||||
# Propshaft is now the default - no configuration needed
|
||||
|
||||
# Assets are automatically fingerprinted and served
|
||||
# Use asset helpers in views:
|
||||
<%= asset_path("application.css") %>
|
||||
<%= asset_path("logo.png") %>
|
||||
```
|
||||
|
||||
## Testing in Rails 8
|
||||
|
||||
### Default Test Suite Setup
|
||||
Rails 8 includes comprehensive testing setup by default:
|
||||
|
||||
```ruby
|
||||
# test/test_helper.rb
|
||||
ENV["RAILS_ENV"] ||= "test"
|
||||
require_relative "../config/environment"
|
||||
require "rails/test_help"
|
||||
|
||||
module ActiveSupport
|
||||
class TestCase
|
||||
# Run tests in parallel with specified workers
|
||||
parallelize(workers: :number_of_processors)
|
||||
|
||||
# Setup all fixtures in test/fixtures/*.yml
|
||||
fixtures :all
|
||||
|
||||
# Add more helper methods to be used by all tests here...
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
### System Testing with Capybara
|
||||
```ruby
|
||||
# test/system/users_test.rb
|
||||
class UsersTest < ApplicationSystemTestCase
|
||||
test "creating a user" do
|
||||
visit new_user_path
|
||||
|
||||
fill_in "Name", with: "John Doe"
|
||||
fill_in "Email", with: "john@example.com"
|
||||
click_button "Create User"
|
||||
|
||||
assert_text "User was successfully created"
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
## Security Best Practices (Rails 8)
|
||||
|
||||
### Built-in Security Features
|
||||
- **Brakeman** - Included by default for security scanning
|
||||
- **Credential Management** - Use `rails credentials:edit`
|
||||
- **Content Security Policy** - Configure in `application_controller.rb`
|
||||
- **Force SSL** - Enable in production environment
|
||||
|
||||
```ruby
|
||||
# config/environments/production.rb
|
||||
config.force_ssl = true
|
||||
config.content_security_policy do |policy|
|
||||
policy.default_src :self, :https
|
||||
policy.font_src :self, :https, :data
|
||||
policy.img_src :self, :https, :data
|
||||
policy.object_src :none
|
||||
policy.script_src :self, :https
|
||||
policy.style_src :self, :https, :unsafe_inline
|
||||
end
|
||||
```
|
||||
|
||||
## Performance Optimization
|
||||
|
||||
### Database Optimization
|
||||
```ruby
|
||||
# Use includes to avoid N+1 queries
|
||||
@users = User.includes(:posts).all
|
||||
|
||||
# Use counter_cache for associations
|
||||
class Post < ApplicationRecord
|
||||
belongs_to :user, counter_cache: true
|
||||
end
|
||||
```
|
||||
|
||||
### Caching Strategies
|
||||
```ruby
|
||||
# Fragment caching in views
|
||||
<% cache @user do %>
|
||||
<%= render @user %>
|
||||
<% end %>
|
||||
|
||||
# Action caching in controllers
|
||||
class UsersController < ApplicationController
|
||||
caches_action :index, expires_in: 1.hour
|
||||
end
|
||||
```
|
||||
|
||||
## Production Deployment Checklist
|
||||
|
||||
### Environment Configuration
|
||||
- [ ] Set `RAILS_ENV=production`
|
||||
- [ ] Configure `RAILS_MASTER_KEY` or `config/credentials.yml.enc`
|
||||
- [ ] Set up database (PostgreSQL/MySQL for production)
|
||||
- [ ] Configure email delivery
|
||||
- [ ] Set up error monitoring (Sentry, Bugsnag)
|
||||
- [ ] Configure logging and log rotation
|
||||
|
||||
### Kamal 2 Deployment
|
||||
- [ ] Update `config/deploy.yml` with production servers
|
||||
- [ ] Set up Docker registry access
|
||||
- [ ] Configure environment variables and secrets
|
||||
- [ ] Run `kamal setup` for initial deployment
|
||||
- [ ] Deploy with `kamal deploy`
|
||||
|
||||
### Performance & Monitoring
|
||||
- [ ] Enable asset precompilation
|
||||
- [ ] Configure CDN for static assets
|
||||
- [ ] Set up application performance monitoring (APM)
|
||||
- [ ] Configure health checks
|
||||
- [ ] Set up backup strategies for database
|
||||
|
||||
## Upgrading to Rails 8
|
||||
|
||||
### Migration Steps
|
||||
1. Update Gemfile: `gem 'rails', '~> 8.0.0'`
|
||||
2. Run `bundle update rails`
|
||||
3. Run `bin/rails app:update` to update configuration files
|
||||
4. Review and update deprecated code
|
||||
5. Update test suite to use new defaults
|
||||
6. Consider migrating to Solid trifecta adapters
|
||||
7. Optionally migrate authentication to native Rails 8 system
|
||||
|
||||
### Breaking Changes to Consider
|
||||
- Ruby 3.2+ requirement
|
||||
- Propshaft replaces Sprockets by default
|
||||
- Some deprecated ActiveRecord methods removed
|
||||
- Updated default configurations for new applications
|
||||
|
||||
This Rails 8 configuration provides a modern, production-ready foundation with the latest Rails features and best practices.
|
||||
@@ -0,0 +1,78 @@
|
||||
{
|
||||
"mcpServers": {
|
||||
"rust-sdk": {
|
||||
"name": "Rust MCP SDK",
|
||||
"description": "Asynchronous, high-performance SDK for Rust",
|
||||
"command": "rust_mcp_server",
|
||||
"args": [],
|
||||
"env": {}
|
||||
},
|
||||
"ht-mcp": {
|
||||
"name": "HT MCP",
|
||||
"description": "Pure Rust implementation for headless terminal interaction",
|
||||
"command": "ht-mcp",
|
||||
"args": [],
|
||||
"env": {}
|
||||
},
|
||||
"rust-docs": {
|
||||
"name": "Rust Docs MCP",
|
||||
"description": "Prevents outdated code suggestions by providing updated Rust docs",
|
||||
"command": "rust-docs-mcp-server",
|
||||
"args": [],
|
||||
"env": {}
|
||||
},
|
||||
"substrate": {
|
||||
"name": "Substrate MCP",
|
||||
"description": "MCP server for interacting with Substrate-based blockchains",
|
||||
"command": "substrate-mcp-rs",
|
||||
"args": [],
|
||||
"env": {
|
||||
"NODE_URL": "..."
|
||||
}
|
||||
},
|
||||
"mcp-proxy": {
|
||||
"name": "MCP Proxy",
|
||||
"description": "Fast Rust-based proxy between stdio and SSE",
|
||||
"command": "mcp-proxy",
|
||||
"args": [],
|
||||
"env": {}
|
||||
},
|
||||
"memory-bank": {
|
||||
"name": "Memory Bank MCP",
|
||||
"description": "Centralized memory system for AI agents",
|
||||
"command": "server-memory",
|
||||
"args": [],
|
||||
"env": {}
|
||||
},
|
||||
"sequential-thinking": {
|
||||
"name": "Sequential Thinking MCP",
|
||||
"description": "Helps LLMs decompose complex tasks into logical steps",
|
||||
"command": "code-reasoning",
|
||||
"args": [],
|
||||
"env": {}
|
||||
},
|
||||
"brave-search": {
|
||||
"name": "Brave Search MCP",
|
||||
"description": "Privacy-focused web search tool",
|
||||
"command": "server-brave-search",
|
||||
"args": [],
|
||||
"env": {}
|
||||
},
|
||||
"google-maps": {
|
||||
"name": "Google Maps MCP",
|
||||
"description": "Integrates Google Maps for geolocation and directions",
|
||||
"command": "server-google-maps",
|
||||
"args": [],
|
||||
"env": {
|
||||
"GOOGLE_MAPS_API_KEY": "..."
|
||||
}
|
||||
},
|
||||
"deep-graph": {
|
||||
"name": "Deep Graph MCP (Code Graph)",
|
||||
"description": "Transforms source code into semantic graphs via DeepGraph",
|
||||
"command": "mcp-code-graph",
|
||||
"args": [],
|
||||
"env": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
# Rust Claude Code Templates
|
||||
|
||||
## Coming Soon! 🚧
|
||||
|
||||
We're actively working on creating comprehensive Claude Code templates for Rust development.
|
||||
|
||||
### What to Expect
|
||||
- Best practices for Rust project structure
|
||||
- Integration with popular Rust tools and frameworks
|
||||
- Workflow optimizations for Rust development
|
||||
- Testing and benchmarking configurations
|
||||
- Cross-compilation and deployment setups
|
||||
- Memory safety and performance guidelines
|
||||
|
||||
### Meanwhile...
|
||||
You can use the [common templates](../common/README.md) as a starting point for your Rust projects. The universal guidelines and git workflows will work well with Rust development.
|
||||
|
||||
### Stay Updated
|
||||
⭐ Star this repository to get notified when the Rust templates are released!
|
||||
|
||||
### Contributing
|
||||
Interested in helping build these templates? We welcome contributions! Please check the main repository's contribution guidelines and feel free to open an issue or pull request.
|
||||
|
||||
---
|
||||
|
||||
*Expected release: Coming soon*
|
||||
Reference in New Issue
Block a user