First Commit
This commit is contained in:
695
docs/CONTRIBUTING.md
Normal file
695
docs/CONTRIBUTING.md
Normal file
@@ -0,0 +1,695 @@
|
||||
# Contributing Guidelines - AI Study Assistant
|
||||
|
||||
## 🤝 HOW TO CONTRIBUTE
|
||||
|
||||
---
|
||||
|
||||
## 📋 OVERVIEW
|
||||
|
||||
Thank you for your interest in contributing to the AI Study Assistant! This guide provides comprehensive information on how to contribute to our project, including development workflows, coding standards, and community guidelines.
|
||||
|
||||
---
|
||||
|
||||
## 🚀 GETTING STARTED
|
||||
|
||||
### Prerequisites
|
||||
- **Git**: Version 2.30 or higher
|
||||
- **Flutter**: Version 3.41.0+
|
||||
- **Node.js**: Version 18.x LTS
|
||||
- **Python**: Version 3.9+ (for RAG engine)
|
||||
- **Firebase CLI**: Latest version
|
||||
- **Code Editor**: VS Code recommended
|
||||
|
||||
### Development Environment Setup
|
||||
1. Fork the repository
|
||||
2. Clone your fork locally
|
||||
3. Set up development environment
|
||||
4. Install dependencies
|
||||
5. Run tests to verify setup
|
||||
|
||||
```bash
|
||||
# Clone your fork
|
||||
git clone https://github.com/YOUR_USERNAME/teachit.git
|
||||
cd teachit
|
||||
|
||||
# Install Flutter dependencies
|
||||
flutter pub get
|
||||
|
||||
# Install Node.js dependencies
|
||||
cd functions && npm install && cd ..
|
||||
|
||||
# Install Python dependencies
|
||||
cd rag_engine && pip install -r requirements.txt && cd ..
|
||||
|
||||
# Run tests
|
||||
flutter test
|
||||
npm test
|
||||
pytest tests/
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🌟 CONTRIBUTION TYPES
|
||||
|
||||
### Code Contributions
|
||||
- **Bug Fixes**: Resolve reported issues
|
||||
- **New Features**: Add functionality
|
||||
- **Performance**: Optimize existing code
|
||||
- **Documentation**: Improve documentation
|
||||
- **Testing**: Add or improve tests
|
||||
|
||||
### Non-Code Contributions
|
||||
- **Bug Reports**: Report issues with detailed information
|
||||
- **Feature Requests**: Suggest new features
|
||||
- **Documentation**: Improve guides and documentation
|
||||
- **Community**: Help others in discussions
|
||||
- **Translation**: Help with internationalization
|
||||
|
||||
---
|
||||
|
||||
## 🔄 DEVELOPMENT WORKFLOW
|
||||
|
||||
### Branch Strategy
|
||||
```
|
||||
main # Production branch
|
||||
├── develop # Development branch
|
||||
├── feature/feature-name # Feature branches
|
||||
├── hotfix/issue-number # Hotfix branches
|
||||
└── release/version # Release branches
|
||||
```
|
||||
|
||||
### Workflow Steps
|
||||
1. **Create Issue**: Discuss changes in an issue first
|
||||
2. **Create Branch**: Create feature branch from `develop`
|
||||
3. **Develop**: Implement changes with proper testing
|
||||
4. **Test**: Ensure all tests pass
|
||||
5. **Submit PR**: Create pull request to `develop`
|
||||
6. **Review**: Get code review and address feedback
|
||||
7. **Merge**: Merge after approval
|
||||
8. **Deploy**: Automatic deployment to staging
|
||||
|
||||
### Branch Naming Conventions
|
||||
- **Feature**: `feature/description-of-feature`
|
||||
- **Bug Fix**: `fix/issue-number-description`
|
||||
- **Hotfix**: `hotfix/issue-number-description`
|
||||
- **Release**: `release/version-number`
|
||||
- **Documentation**: `docs/description-of-change`
|
||||
|
||||
---
|
||||
|
||||
## 📝 CODING STANDARDS
|
||||
|
||||
### General Guidelines
|
||||
- **Consistency**: Follow existing code style
|
||||
- **Clarity**: Write clear, readable code
|
||||
- **Comments**: Add comments for complex logic
|
||||
- **Testing**: Write tests for new functionality
|
||||
- **Documentation**: Update relevant documentation
|
||||
|
||||
### Flutter/Dart Standards
|
||||
|
||||
#### Code Style
|
||||
```dart
|
||||
// Use meaningful variable names
|
||||
final List<LearningConcept> concepts = [];
|
||||
|
||||
// Use const for compile-time constants
|
||||
const int maxRetries = 3;
|
||||
|
||||
// Use proper naming conventions
|
||||
class LearningAnalytics {
|
||||
Future<void> trackProgress() async {
|
||||
// Implementation
|
||||
}
|
||||
}
|
||||
|
||||
// Use async/await for asynchronous operations
|
||||
Future<void> loadData() async {
|
||||
final data = await apiService.getData();
|
||||
processData(data);
|
||||
}
|
||||
```
|
||||
|
||||
#### Widget Structure
|
||||
```dart
|
||||
class CustomWidget extends StatelessWidget {
|
||||
final String title;
|
||||
final VoidCallback? onTap;
|
||||
|
||||
const CustomWidget({
|
||||
required this.title,
|
||||
this.onTap,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
child: Text(title),
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### State Management
|
||||
```dart
|
||||
// Use Riverpod providers
|
||||
final learningStateProvider = StateNotifierProvider<LearningStateNotifier, LearningState>(
|
||||
(ref) => LearningStateNotifier(),
|
||||
);
|
||||
|
||||
// Use proper provider naming
|
||||
final authProvider = Provider<AuthService>((ref) => AuthService());
|
||||
```
|
||||
|
||||
### TypeScript/Node.js Standards
|
||||
|
||||
#### Code Style
|
||||
```typescript
|
||||
// Use interfaces for type definitions
|
||||
interface User {
|
||||
id: string;
|
||||
email: string;
|
||||
role: UserRole;
|
||||
}
|
||||
|
||||
// Use proper error handling
|
||||
async function getUser(id: string): Promise<User> {
|
||||
try {
|
||||
const user = await userRepository.findById(id);
|
||||
if (!user) {
|
||||
throw new Error('User not found');
|
||||
}
|
||||
return user;
|
||||
} catch (error) {
|
||||
console.error('Error fetching user:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// Use proper function naming
|
||||
export class UserService {
|
||||
async createUser(userData: CreateUserRequest): Promise<User> {
|
||||
// Implementation
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Python Standards
|
||||
|
||||
#### Code Style
|
||||
```python
|
||||
# Use type hints
|
||||
from typing import List, Optional
|
||||
|
||||
class VectorSearchService:
|
||||
def __init__(self, dimension: int = 384) -> None:
|
||||
self.dimension = dimension
|
||||
|
||||
def search(self, query: np.ndarray, k: int = 10) -> List[Tuple[int, float]]:
|
||||
"""Search for similar vectors."""
|
||||
# Implementation
|
||||
pass
|
||||
|
||||
# Use proper docstrings
|
||||
def process_text(text: str) -> ProcessedText:
|
||||
"""
|
||||
Process text for embedding.
|
||||
|
||||
Args:
|
||||
text: The input text to process
|
||||
|
||||
Returns:
|
||||
ProcessedText: The processed text object
|
||||
"""
|
||||
# Implementation
|
||||
pass
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🧪 TESTING GUIDELINES
|
||||
|
||||
### Test Structure
|
||||
```
|
||||
test/
|
||||
├── unit/ # Unit tests
|
||||
├── widget/ # Widget tests
|
||||
├── integration/ # Integration tests
|
||||
├── e2e/ # End-to-end tests
|
||||
└── fixtures/ # Test data
|
||||
```
|
||||
|
||||
### Flutter Testing
|
||||
```dart
|
||||
// Unit test example
|
||||
void main() {
|
||||
group('LearningStateNotifier', () {
|
||||
test('should update learning state correctly', () async {
|
||||
final notifier = LearningStateNotifier();
|
||||
|
||||
await notifier.updateProgress('mathematics', 0.8);
|
||||
|
||||
expect(notifier.state.mastery['mathematics'], equals(0.8));
|
||||
});
|
||||
|
||||
test('should handle invalid input gracefully', () async {
|
||||
final notifier = LearningStateNotifier();
|
||||
|
||||
expect(() => notifier.updateProgress('', -1.0), throwsA(isA<ArgumentError>));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Widget test example
|
||||
void main() {
|
||||
testWidgets('LearningProgressCard displays correctly', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: LearningProgressCard(
|
||||
concept: 'Derivatives',
|
||||
mastery: 0.75,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
expect(find.text('Derivatives'), findsOneWidget);
|
||||
expect(find.text('75%'), findsOneWidget);
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
### Backend Testing
|
||||
```typescript
|
||||
// Unit test example
|
||||
describe('UserService', () => {
|
||||
let userService: UserService;
|
||||
let mockRepository: jest.Mocked<UserRepository>;
|
||||
|
||||
beforeEach(() => {
|
||||
mockRepository = new MockUserRepository();
|
||||
userService = new UserService(mockRepository);
|
||||
});
|
||||
|
||||
it('should create user successfully', async () => {
|
||||
const userData = {
|
||||
email: 'test@example.com',
|
||||
role: 'student',
|
||||
};
|
||||
|
||||
const expectedUser = { id: '123', ...userData };
|
||||
mockRepository.create.mockResolvedValue(expectedUser);
|
||||
|
||||
const result = await userService.createUser(userData);
|
||||
|
||||
expect(result).toEqual(expectedUser);
|
||||
expect(mockRepository.create).toHaveBeenCalledWith(userData);
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
### Python Testing
|
||||
```python
|
||||
# Unit test example
|
||||
import pytest
|
||||
from unittest.mock import Mock, patch
|
||||
|
||||
class TestVectorSearchService:
|
||||
def setup_method(self):
|
||||
self.service = VectorSearchService(dimension=384)
|
||||
self.mock_index = Mock()
|
||||
self.service.index = self.mock_index
|
||||
|
||||
def test_search_returns_correct_results(self):
|
||||
# Arrange
|
||||
query = np.random.rand(384)
|
||||
expected_results = [(1, 0.95), (2, 0.87)]
|
||||
self.mock_index.search.return_value = (np.array([0.95, 0.87]), np.array([1, 2]))
|
||||
|
||||
# Act
|
||||
results = self.service.search(query, k=2)
|
||||
|
||||
# Assert
|
||||
assert len(results) == 2
|
||||
assert results == expected_results
|
||||
self.mock_index.search.assert_called_once()
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📖 DOCUMENTATION STANDARDS
|
||||
|
||||
### Documentation Types
|
||||
- **API Documentation**: Endpoint specifications
|
||||
- **Code Comments**: Inline documentation
|
||||
- **README Files**: Project and module documentation
|
||||
- **User Guides**: End-user documentation
|
||||
- **Developer Guides**: Technical documentation
|
||||
|
||||
### Writing Guidelines
|
||||
- **Clear and Concise**: Use simple, clear language
|
||||
- **Examples**: Include code examples
|
||||
- **Structure**: Use proper headings and formatting
|
||||
- **Consistency**: Follow existing documentation style
|
||||
- **Accuracy**: Ensure information is up-to-date
|
||||
|
||||
### Code Comments
|
||||
```dart
|
||||
/// A service for managing learning analytics and progress tracking.
|
||||
///
|
||||
/// This service provides functionality to:
|
||||
/// - Track student progress across concepts
|
||||
/// - Calculate mastery levels
|
||||
/// - Generate learning recommendations
|
||||
///
|
||||
/// Example:
|
||||
/// ```dart
|
||||
/// final analytics = LearningAnalyticsService();
|
||||
/// await analytics.trackProgress('mathematics', 0.8);
|
||||
/// ```
|
||||
class LearningAnalyticsService {
|
||||
/// The maximum number of concepts to track per student.
|
||||
static const int maxConceptsPerStudent = 50;
|
||||
|
||||
/// Tracks progress for a specific concept.
|
||||
///
|
||||
/// [studentId] The unique identifier for the student
|
||||
/// [concept] The concept being tracked
|
||||
/// [mastery] The mastery level (0.0 to 1.0)
|
||||
///
|
||||
/// Throws [ArgumentError] if mastery is not in valid range
|
||||
Future<void> trackProgress(
|
||||
String studentId,
|
||||
String concept,
|
||||
double mastery
|
||||
) async {
|
||||
if (mastery < 0.0 || mastery > 1.0) {
|
||||
throw ArgumentError('Mastery must be between 0.0 and 1.0');
|
||||
}
|
||||
|
||||
// Implementation
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔍 CODE REVIEW PROCESS
|
||||
|
||||
### Review Criteria
|
||||
- **Functionality**: Does the code work as expected?
|
||||
- **Code Quality**: Is the code well-written and maintainable?
|
||||
- **Testing**: Are there adequate tests?
|
||||
- **Documentation**: Is the code properly documented?
|
||||
- **Performance**: Is the code efficient?
|
||||
- **Security**: Is the code secure?
|
||||
|
||||
### Review Guidelines
|
||||
- **Be Constructive**: Provide helpful, specific feedback
|
||||
- **Be Thorough**: Review all aspects of the code
|
||||
- **Be Respectful**: Maintain a professional tone
|
||||
- **Be Timely**: Respond to reviews promptly
|
||||
|
||||
### Pull Request Template
|
||||
```markdown
|
||||
## Description
|
||||
Brief description of changes
|
||||
|
||||
## Type of Change
|
||||
- [ ] Bug fix
|
||||
- [ ] New feature
|
||||
- [ ] Breaking change
|
||||
- [ ] Documentation update
|
||||
|
||||
## Testing
|
||||
- [ ] All tests pass
|
||||
- [ ] New tests added
|
||||
- [ ] Manual testing completed
|
||||
|
||||
## Checklist
|
||||
- [ ] Code follows project style guidelines
|
||||
- [ ] Self-review completed
|
||||
- [ ] Documentation updated
|
||||
- [ ] No breaking changes (or documented)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🐛 BUG REPORTING
|
||||
|
||||
### Bug Report Template
|
||||
```markdown
|
||||
## Bug Description
|
||||
Clear and concise description of the bug
|
||||
|
||||
## Steps to Reproduce
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
|
||||
## Expected Behavior
|
||||
What you expected to happen
|
||||
|
||||
## Actual Behavior
|
||||
What actually happened
|
||||
|
||||
## Screenshots
|
||||
If applicable, add screenshots
|
||||
|
||||
## Environment
|
||||
- OS: [e.g. iOS 15.0, Android 11, Windows 10]
|
||||
- App Version: [e.g. 1.0.0]
|
||||
- Browser: [e.g. Chrome 96.0]
|
||||
- Device: [e.g. iPhone 12, Samsung Galaxy S21]
|
||||
|
||||
## Additional Context
|
||||
Add any other context about the problem here
|
||||
```
|
||||
|
||||
### Bug Severity Levels
|
||||
- **Critical**: Blocks core functionality
|
||||
- **High**: Significant impact on user experience
|
||||
- **Medium**: Minor issues with workarounds
|
||||
- **Low**: Cosmetic or minor issues
|
||||
|
||||
---
|
||||
|
||||
## 💡 FEATURE REQUESTS
|
||||
|
||||
### Feature Request Template
|
||||
```markdown
|
||||
## Feature Description
|
||||
Clear and concise description of the feature
|
||||
|
||||
## Problem Statement
|
||||
What problem does this feature solve?
|
||||
|
||||
## Proposed Solution
|
||||
How should this feature work?
|
||||
|
||||
## Alternatives
|
||||
What other solutions have you considered?
|
||||
|
||||
## Additional Context
|
||||
Add any other context about the feature here
|
||||
```
|
||||
|
||||
### Feature Priority Levels
|
||||
- **High**: Core functionality needed
|
||||
- **Medium**: Important enhancement
|
||||
- **Low**: Nice to have feature
|
||||
|
||||
---
|
||||
|
||||
## 🌍 INTERNATIONALIZATION
|
||||
|
||||
### Translation Guidelines
|
||||
- **Languages**: Currently supporting English, Portuguese, Spanish
|
||||
- **Translation Files**: Located in `lib/l10n/`
|
||||
- **Translation Keys**: Use descriptive keys
|
||||
- **Context**: Provide context for translators
|
||||
|
||||
### Adding New Language
|
||||
1. Create new `.arb` file in `lib/l10n/`
|
||||
2. Translate all keys from existing files
|
||||
3. Update `app_localizations.dart`
|
||||
4. Test with new language
|
||||
|
||||
---
|
||||
|
||||
## 🔐 SECURITY GUIDELINES
|
||||
|
||||
### Security Considerations
|
||||
- **No Hardcoded Secrets**: Use environment variables
|
||||
- **Input Validation**: Validate all user inputs
|
||||
- **Authentication**: Use proper authentication
|
||||
- **Authorization**: Implement proper access controls
|
||||
- **Data Protection**: Encrypt sensitive data
|
||||
|
||||
### Reporting Security Issues
|
||||
- **Private**: Report security issues privately
|
||||
- **Email**: security@teachit.app
|
||||
- **PGP Key**: Available on request
|
||||
- **Response**: Within 24 hours
|
||||
|
||||
---
|
||||
|
||||
## 📊 PERFORMANCE GUIDELINES
|
||||
|
||||
### Performance Considerations
|
||||
- **Efficiency**: Write efficient code
|
||||
- **Memory**: Manage memory usage
|
||||
- **Network**: Optimize network requests
|
||||
- **UI**: Maintain smooth UI performance
|
||||
- **Battery**: Consider battery usage
|
||||
|
||||
### Performance Testing
|
||||
- **Profiling**: Use profiling tools
|
||||
- **Benchmarks**: Measure performance
|
||||
- **Monitoring**: Track performance metrics
|
||||
- **Optimization**: Optimize bottlenecks
|
||||
|
||||
---
|
||||
|
||||
## 🤝 COMMUNITY GUIDELINES
|
||||
|
||||
### Code of Conduct
|
||||
- **Respect**: Treat everyone with respect
|
||||
- **Inclusive**: Be inclusive and welcoming
|
||||
- **Professional**: Maintain professional conduct
|
||||
- **Helpful**: Help others learn and grow
|
||||
|
||||
### Communication Channels
|
||||
- **GitHub Issues**: For bug reports and feature requests
|
||||
- **GitHub Discussions**: For general discussions
|
||||
- **Discord**: For real-time chat (if available)
|
||||
- **Email**: For private communications
|
||||
|
||||
### Getting Help
|
||||
- **Documentation**: Check documentation first
|
||||
- **Issues**: Search existing issues
|
||||
- **Discussions**: Ask in discussions
|
||||
- **Maintainers**: Contact maintainers directly
|
||||
|
||||
---
|
||||
|
||||
## 📋 RELEASE PROCESS
|
||||
|
||||
### Release Checklist
|
||||
- [ ] All tests pass
|
||||
- [ ] Documentation updated
|
||||
- [ ] Version number updated
|
||||
- [ ] Change log updated
|
||||
- [ ] Security review completed
|
||||
- [ ] Performance testing completed
|
||||
- [ ] Integration testing completed
|
||||
|
||||
### Release Types
|
||||
- **Major**: Significant new features (X.0.0)
|
||||
- **Minor**: New features and improvements (X.Y.0)
|
||||
- **Patch**: Bug fixes and security updates (X.Y.Z)
|
||||
|
||||
### Version Numbering
|
||||
- **Semantic Versioning**: Follow SemVer
|
||||
- **Breaking Changes**: Increment major version
|
||||
- **New Features**: Increment minor version
|
||||
- **Bug Fixes**: Increment patch version
|
||||
|
||||
---
|
||||
|
||||
## 🏆 RECOGNITION
|
||||
|
||||
### Contributor Recognition
|
||||
- **Contributors List**: All contributors listed
|
||||
- **Release Notes**: Contributors mentioned in release notes
|
||||
- **Community**: Recognition in community forums
|
||||
- **Swag**: Available for significant contributions
|
||||
|
||||
### Types of Contributions
|
||||
- **Code**: Code contributions
|
||||
- **Documentation**: Documentation improvements
|
||||
- **Design**: UI/UX contributions
|
||||
- **Testing**: Testing and quality assurance
|
||||
- **Community**: Community support and help
|
||||
|
||||
---
|
||||
|
||||
## 📚 RESOURCES
|
||||
|
||||
### Learning Resources
|
||||
- [Flutter Documentation](https://flutter.dev/docs)
|
||||
- [Firebase Documentation](https://firebase.google.com/docs)
|
||||
- [TypeScript Handbook](https://www.typescriptlang.org/docs/)
|
||||
- [Python Style Guide](https://pep8.org/)
|
||||
- [Git Handbook](https://git-scm.com/doc)
|
||||
|
||||
### Development Tools
|
||||
- **IDE**: VS Code with Flutter and Dart extensions
|
||||
- **Testing**: Flutter Test, Jest, Pytest
|
||||
- **Linting**: Dart Analysis, ESLint, Flake8
|
||||
- **Formatting**: Dart Format, Prettier, Black
|
||||
- **Debugging**: Flutter DevTools, Chrome DevTools
|
||||
|
||||
### Community Resources
|
||||
- [Flutter Community](https://flutter.dev/community)
|
||||
- [Firebase Community](https://firebase.google.com/community)
|
||||
- [Stack Overflow](https://stackoverflow.com/questions/tagged/teachit)
|
||||
- [Discord Server](https://discord.gg/teachit)
|
||||
|
||||
---
|
||||
|
||||
## 📞 GETTING HELP
|
||||
|
||||
### Ways to Get Help
|
||||
- **Documentation**: Check existing documentation
|
||||
- **Issues**: Search existing issues
|
||||
- **Discussions**: Ask in GitHub discussions
|
||||
- **Email**: Contact maintainers at dev@teachit.app
|
||||
- **Discord**: Join our Discord server
|
||||
|
||||
### When to Ask for Help
|
||||
- **Setup Issues**: Problems with development environment
|
||||
- **Code Issues**: Help with code implementation
|
||||
- **Testing**: Help with writing tests
|
||||
- **Documentation**: Help with documentation
|
||||
- **Review**: Request code review
|
||||
|
||||
---
|
||||
|
||||
## ✅ CONTRIBUTOR CHECKLIST
|
||||
|
||||
### Before Contributing
|
||||
- [ ] Read contributing guidelines
|
||||
- [ ] Set up development environment
|
||||
- [ ] Understand project structure
|
||||
- [ ] Check existing issues and discussions
|
||||
- [ ] Choose appropriate contribution type
|
||||
|
||||
### During Development
|
||||
- [ ] Follow coding standards
|
||||
- [ ] Write tests for new code
|
||||
- [ ] Update documentation
|
||||
- [ ] Test thoroughly
|
||||
- [ ] Keep commits small and focused
|
||||
|
||||
### Before Submitting
|
||||
- [ ] Run all tests
|
||||
- [ ] Update change log
|
||||
- [ ] Write clear commit messages
|
||||
- [ ] Create descriptive pull request
|
||||
- [ ] Address review feedback
|
||||
|
||||
---
|
||||
|
||||
## 🎉 THANK YOU
|
||||
|
||||
Thank you for contributing to the AI Study Assistant! Your contributions help make educational technology better for students and teachers worldwide.
|
||||
|
||||
Every contribution, no matter how small, is valued and appreciated. Together, we're building a platform that makes learning more accessible, engaging, and effective for everyone.
|
||||
|
||||
---
|
||||
|
||||
*Last Updated: 2026-05-06*
|
||||
*Version: 1.0.0*
|
||||
*Community Team: Open Source & Collaboration*
|
||||
Reference in New Issue
Block a user