Development Guide
Comprehensive guide for developers who want to contribute to or extend the Mattermost PDF Dekont Parser Plugin.
Table of contents
- Development Environment Setup
- Project Structure
- Core Architecture
- Development Workflow
- Adding Bank Support
- Debugging and Testing
- Performance Optimization
- CI/CD Pipeline
- Security Guidelines
- Release Process
- Contributing Guidelines
- Resources
- Support
Development Environment Setup
Prerequisites
Before starting development, ensure you have:
- Go 1.19+: Download Go
- Git: For version control
- Make: For build automation (available on most Linux systems)
- Mattermost Server: For testing (development instance recommended)
Environment Verification
# Check Go version
go version
# Check Git
git --version
# Check Make
make --version
Clone and Setup
# Clone the repository
git clone https://github.com/SkyLostTR/mattermost-dekont-plugin.git
cd mattermost-dekont-plugin
# Install dependencies
go mod download
# Verify build
make build
Project Structure
Core Files
mattermost-dekont-plugin/
├── plugin.go # Main plugin logic
├── plugin_test.go # Comprehensive test suite
├── plugin.json # Plugin manifest
├── go.mod # Go module definition
├── go.sum # Dependency checksums
├── Makefile # Build automation
└── README.md # Project documentation
Configuration Files
├── .github/
│ ├── workflows/ # CI/CD pipelines
│ ├── ISSUE_TEMPLATE/ # Issue templates
│ └── pull_request_template.md
├── .golangci.yml # Linting configuration
├── .editorconfig # Editor settings
└── sonar-project.properties # Code quality
Build Artifacts
├── dist/ # Distribution files
├── plugin # Built Linux plugin binary
└── *.tar.gz # Plugin bundles
Core Architecture
Plugin Framework
The plugin extends Mattermost’s plugin framework:
type Plugin struct {
plugin.MattermostPlugin
// Plugin-specific fields
}
// Required plugin hooks
func (p *Plugin) OnActivate() error
func (p *Plugin) MessageHasBeenPosted(c *plugin.Context, post *model.Post)
PDF Processing Pipeline
- File Detection: Monitor file uploads via
MessageHasBeenPosted
- PDF Validation: Verify file type and readability
- Text Extraction: Use
github.com/ledongthuc/pdf
library - Pattern Matching: Apply regex patterns for field extraction
- Result Formatting: Create structured output
- Message Update: Modify original post with extracted data
Key Components
File Handler
func (p *Plugin) handleFileUpload(post *model.Post, fileInfo *model.FileInfo) error {
// Download file content
// Validate PDF format
// Process PDF
// Update post
}
PDF Processor
func (p *Plugin) processPDF(content []byte) (*TransactionInfo, error) {
// Extract text from PDF
// Apply pattern matching
// Clean and validate results
}
Pattern Matcher
type FieldPattern struct {
Name string
Regex *regexp.Regexp
Cleaner func(string) string
}
Development Workflow
Coding Standards
Follow Go best practices and project-specific standards:
Code Style
// ✅ Good: Clear function names and error handling
func (p *Plugin) extractTransactionAmount(text string) (string, error) {
pattern := regexp.MustCompile(`(?i)(TUTAR|İŞLEM TUTARI):\s*([0-9.,]+\s*[A-Z]+)`)
matches := pattern.FindStringSubmatch(text)
if len(matches) < 3 {
return "", errors.New("amount not found")
}
return strings.TrimSpace(matches[2]), nil
}
// ❌ Bad: Unclear naming and poor error handling
func (p *Plugin) getAmt(s string) string {
r, _ := regexp.Compile(`(?i)(TUTAR|İŞLEM TUTARI):\s*([0-9.,]+\s*[A-Z]+)`)
m := r.FindStringSubmatch(s)
return m[2]
}
Error Handling
// Always log errors with context
if err != nil {
p.API.LogError("Failed to process PDF", "error", err.Error(), "filename", fileInfo.Name)
return err
}
Resource Management
// Always clean up resources
defer func() {
if tempFile != nil {
os.Remove(tempFile.Name())
}
}()
Testing Strategy
Unit Tests
Use table-driven tests for comprehensive coverage:
func TestExtractRecipient(t *testing.T) {
tests := []struct {
name string
input string
expected string
hasError bool
}{
{
name: "İş Bankası format",
input: "ALICI: AHMET YILMAZ",
expected: "AHMET YILMAZ",
hasError: false,
},
{
name: "Garanti format",
input: "ALAN: FATMA KAYA",
expected: "FATMA KAYA",
hasError: false,
},
{
name: "No recipient found",
input: "Some random text",
expected: "",
hasError: true,
},
}
p := &Plugin{}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result, err := p.extractRecipient(tt.input)
if tt.hasError && err == nil {
t.Errorf("Expected error but got none")
}
if !tt.hasError && err != nil {
t.Errorf("Unexpected error: %v", err)
}
if result != tt.expected {
t.Errorf("Expected %q, got %q", tt.expected, result)
}
})
}
}
Benchmark Tests
func BenchmarkProcessPDF(b *testing.B) {
plugin := &Plugin{}
pdfData := loadTestPDF("sample_receipt.pdf")
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, _ = plugin.processPDF(pdfData)
}
}
Build Process
Local Development
# Build plugin
make build
# Run tests
go test -v
# Run with coverage
go test -cover
# Run benchmarks
go test -bench=.
# Create bundle
make bundle
Using Make Targets
# Clean previous builds
make clean
# Install dependencies
make deps
# Lint code
make lint
# Format code
make fmt
# Run all checks
make check
Adding Bank Support
Step 1: Analyze Bank Format
- Collect Sample PDFs: Get various receipt types from the bank
- Identify Patterns: Look for consistent field labels and formats
- Document Variations: Note different ways fields might appear
Step 2: Create Pattern Rules
// Add to field extraction patterns
var recipientPatterns = []FieldPattern{
{
Name: "Standard Alıcı",
Regex: regexp.MustCompile(`(?i)ALICI[^:]*:\s*(.+)`),
Cleaner: cleanRecipientName,
},
{
Name: "New Bank Format",
Regex: regexp.MustCompile(`(?i)HESAP SAHİBİ[^:]*:\s*(.+)`),
Cleaner: cleanRecipientName,
},
}
Step 3: Write Tests
func TestNewBankExtraction(t *testing.T) {
tests := []struct {
name string
pdfText string
expected TransactionInfo
}{
{
name: "New Bank Standard Receipt",
pdfText: loadTestPDFText("newbank_sample.txt"),
expected: TransactionInfo{
Recipient: "JOHN DOE",
Description: "Payment Description",
Amount: "1,000.00 TL",
},
},
}
// Test implementation...
}
Step 4: Update Documentation
- Add bank to supported list
- Document field patterns
- Include sample output
- Update README.md
Debugging and Testing
Local Testing Setup
- Development Mattermost Instance:
# Using Docker docker run --name mattermost-dev -d --publish 8065:8065 mattermost/mattermost-preview
- Plugin Installation:
- Build plugin bundle
- Upload via System Console
- Enable plugin
- Test with sample PDFs
Debug Logging
Enable detailed logging:
// Add debug logs throughout processing
p.API.LogDebug("Processing PDF", "filename", fileInfo.Name, "size", len(content))
p.API.LogDebug("Extracted text", "length", len(text), "preview", text[:min(100, len(text))])
p.API.LogDebug("Pattern match result", "field", "recipient", "value", recipient)
Common Issues
PDF Text Extraction
// Handle PDF extraction errors gracefully
func (p *Plugin) extractPDFText(content []byte) (string, error) {
reader, err := pdf.NewReader(bytes.NewReader(content), int64(len(content)))
if err != nil {
return "", fmt.Errorf("failed to create PDF reader: %w", err)
}
var text strings.Builder
for i := 1; i <= reader.NumPage(); i++ {
page, err := reader.Page(i)
if err != nil {
p.API.LogWarn("Failed to read page", "page", i, "error", err.Error())
continue
}
pageText, err := page.GetPlainText()
if err != nil {
p.API.LogWarn("Failed to extract text from page", "page", i, "error", err.Error())
continue
}
text.WriteString(pageText)
}
return text.String(), nil
}
Performance Optimization
Memory Management
// Process large PDFs efficiently
func (p *Plugin) processLargePDF(content []byte) error {
// Limit PDF size
if len(content) > maxPDFSize {
return errors.New("PDF too large")
}
// Use streaming where possible
reader := bytes.NewReader(content)
// Clean up resources
defer func() {
content = nil
runtime.GC()
}()
return nil
}
Concurrent Processing
// Handle multiple uploads efficiently
func (p *Plugin) processMultipleFiles(posts []*model.Post) {
semaphore := make(chan struct{}, maxConcurrentProcessing)
var wg sync.WaitGroup
for _, post := range posts {
wg.Add(1)
go func(p *model.Post) {
defer wg.Done()
semaphore <- struct{}{}
defer func() { <-semaphore }()
p.processPost(post)
}(post)
}
wg.Wait()
}
CI/CD Pipeline
GitHub Actions Workflow
The project uses automated CI/CD:
# .github/workflows/ci.yml
name: CI
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-go@v3
with:
go-version: '1.19'
- name: Run tests
run: go test -v -cover ./...
- name: Build
run: make build
Code Quality Checks
# .github/workflows/code-quality.yml
- name: Run golangci-lint
uses: golangci/golangci-lint-action@v3
with:
version: latest
- name: SonarCloud Scan
uses: SonarSource/sonarcloud-github-action@master
Security Guidelines
Secure Coding Practices
- Input Validation:
// Validate file types and sizes if !isValidPDF(content) { return errors.New("invalid PDF format") }
- No Sensitive Data Logging:
// ❌ Never log sensitive information p.API.LogInfo("Processing transaction", "amount", amount, "recipient", recipient) // ✅ Log only metadata p.API.LogInfo("Processing transaction", "fieldCount", len(fields))
- Resource Limits:
// Prevent resource exhaustion const ( maxPDFSize = 10 * 1024 * 1024 // 10MB maxProcessingTime = 30 * time.Second )
Dependency Security
- Regular dependency updates via Dependabot
- Security scanning with Trivy
- Vulnerability monitoring via GitHub Security
Release Process
Version Management
Follow semantic versioning:
- Major: Breaking changes
- Minor: New features, backward compatible
- Patch: Bug fixes
Release Checklist
- Update Version:
plugin.json
: Update version fieldCHANGELOG.md
: Document changesREADME.md
: Update compatibility info
- Run Tests:
make test make lint make build
- Create Release:
- Tag version:
git tag v1.x.x
- Push tags:
git push --tags
- GitHub Actions automatically creates release
- Tag version:
- Post-Release:
- Update documentation
- Notify community
- Monitor for issues
Contributing Guidelines
Pull Request Process
- Fork and Branch:
git checkout -b feature/new-bank-support
- Development:
- Write code following standards
- Add comprehensive tests
- Update documentation
- Testing:
make test make lint make build
- Submit PR:
- Use PR template
- Include test results
- Request review
Code Review Checklist
- Tests added for new functionality
- Documentation updated
- Security considerations addressed
- Performance impact assessed
- Error handling implemented
- Turkish character encoding handled
- Bank format compatibility verified
Resources
Development Tools
- IDE Setup: VS Code with Go extension
- Debugging: Delve debugger
- Testing: Built-in Go testing framework
- Profiling: pprof for performance analysis
Documentation
Community
Support
Need help with development?