276 lines
7.6 KiB
Markdown
276 lines
7.6 KiB
Markdown
# Code Duplication Refactoring - Quick Reference Guide
|
|
|
|
## Top 3 Quick Wins (Do These First!)
|
|
|
|
### 1. Remove Duplicate Type Conversion Function (15 minutes)
|
|
**File:** `/Users/igodwin/Workspace/notifier/api/grpc/handler.go`
|
|
|
|
**Problem:** Function `convertDomainToProtoType` (lines 331-344) is an exact duplicate of `convertDomainTypeToProto` (lines 294-307).
|
|
|
|
**Action:**
|
|
1. Delete lines 331-344
|
|
2. Replace call at line 368:
|
|
```diff
|
|
- Type: convertDomainToProtoType(notif.Type),
|
|
+ Type: convertDomainTypeToProto(notif.Type),
|
|
```
|
|
|
|
**Result:** Removes 14 lines of duplicate code
|
|
|
|
---
|
|
|
|
### 2. Extract Notifier Validation Pattern (30 minutes)
|
|
**Files:**
|
|
- `/Users/igodwin/Workspace/notifier/internal/notifier/smtp.go` (lines 63-70)
|
|
- `/Users/igodwin/Workspace/notifier/internal/notifier/slack.go` (lines 78-85)
|
|
- `/Users/igodwin/Workspace/notifier/internal/notifier/ntfy.go` (lines 185-192)
|
|
- `/Users/igodwin/Workspace/notifier/internal/notifier/stdout.go` (lines 26-33)
|
|
|
|
**Problem:** All 4 notifiers have identical validation at start of Send():
|
|
```go
|
|
if err := ValidateContext(ctx); err != nil {
|
|
return nil, err
|
|
}
|
|
if err := s.Validate(notification); err != nil {
|
|
return nil, err
|
|
}
|
|
```
|
|
|
|
**Action:**
|
|
1. Add to `/Users/igodwin/Workspace/notifier/internal/notifier/notifier.go`:
|
|
```go
|
|
// ValidateNotification validates context and notification for sending
|
|
func ValidateNotification(ctx context.Context, n *domain.Notification, validator domain.Notifier) error {
|
|
if err := ValidateContext(ctx); err != nil {
|
|
return err
|
|
}
|
|
return validator.Validate(n)
|
|
}
|
|
```
|
|
|
|
2. Replace validation blocks in all 4 files with:
|
|
```go
|
|
if err := ValidateNotification(ctx, notification, s); err != nil {
|
|
return nil, err
|
|
}
|
|
```
|
|
|
|
**Result:** Reduces duplication from 4 places to 1, easier maintenance
|
|
|
|
---
|
|
|
|
### 3. Add NotificationResult Error Helper (30 minutes)
|
|
**Files:**
|
|
- `/Users/igodwin/Workspace/notifier/internal/notifier/smtp.go` (multiple locations)
|
|
- `/Users/igodwin/Workspace/notifier/internal/notifier/slack.go` (multiple locations)
|
|
- `/Users/igodwin/Workspace/notifier/internal/notifier/ntfy.go` (multiple locations)
|
|
|
|
**Problem:** Repeated error result creation pattern in all notifiers
|
|
|
|
**Action:**
|
|
1. Add to `BaseNotifier` struct:
|
|
```go
|
|
func (b *BaseNotifier) ErrorResult(notificationID, msg string) *domain.NotificationResult {
|
|
return &domain.NotificationResult{
|
|
NotificationID: notificationID,
|
|
Success: false,
|
|
Error: msg,
|
|
SentAt: time.Now(),
|
|
}
|
|
}
|
|
```
|
|
|
|
2. Replace error creation calls with:
|
|
```go
|
|
return b.ErrorResult(notification.ID, err.Error()), err
|
|
```
|
|
|
|
**Result:** Consistent error handling across all notifiers
|
|
|
|
---
|
|
|
|
## Medium-Effort Improvements
|
|
|
|
### 4. Refactor Filter Matching (40 minutes)
|
|
**File:** `/Users/igodwin/Workspace/notifier/internal/service/service.go` (lines 481-540)
|
|
|
|
**Problem:** 60+ lines of repetitive filter matching logic
|
|
|
|
**Solution:** Extract helper functions:
|
|
```go
|
|
func contains[T comparable](items []T, target T) bool {
|
|
for _, item := range items {
|
|
if item == target {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func notificationHasRecipient(n *domain.Notification, recipients []string) bool {
|
|
for _, fr := range recipients {
|
|
if contains(n.Recipients, fr) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
```
|
|
|
|
Then simplify `matchesFilter` to use these helpers.
|
|
|
|
**Result:** Reduces 60+ lines to ~30, easier to extend
|
|
|
|
---
|
|
|
|
### 5. Extract Auth Validation (50 minutes)
|
|
**Files:**
|
|
- `/Users/igodwin/Workspace/notifier/internal/auth/rest_middleware.go` (lines 35-50)
|
|
- `/Users/igodwin/Workspace/notifier/internal/auth/grpc_middleware.go` (lines 38-50, 82-94)
|
|
|
|
**Problem:** Same validation logic in 3 places
|
|
|
|
**Solution:** Add to `APIKeyStore`:
|
|
```go
|
|
type ValidatedKey struct {
|
|
APIKey *APIKey
|
|
ClientID string
|
|
Roles []string
|
|
}
|
|
|
|
func (s *APIKeyStore) ValidateAndAuthorize(keyStr string) (*ValidatedKey, error) {
|
|
key, err := s.ValidateKey(keyStr)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
allowed, err := s.CheckRateLimit(keyStr)
|
|
if err != nil || !allowed {
|
|
return nil, fmt.Errorf("rate limit exceeded")
|
|
}
|
|
|
|
if err := s.UpdateLastUsed(keyStr); err != nil {
|
|
// Log warning but continue
|
|
}
|
|
|
|
return &ValidatedKey{
|
|
APIKey: key,
|
|
ClientID: key.ClientID,
|
|
Roles: key.Roles,
|
|
}, nil
|
|
}
|
|
```
|
|
|
|
Use in both middleware implementations.
|
|
|
|
**Result:** Consistent auth logic, easier to update validation rules
|
|
|
|
---
|
|
|
|
### 6. Create HTTP Request Helper (45 minutes)
|
|
**Files:**
|
|
- `/Users/igodwin/Workspace/notifier/internal/notifier/slack.go` (sendToSlack)
|
|
- `/Users/igodwin/Workspace/notifier/internal/notifier/ntfy.go` (sendToTopic)
|
|
|
|
**Problem:** 25+ lines of identical HTTP handling
|
|
|
|
**Solution:** Add to notifier package:
|
|
```go
|
|
func sendJSONPostRequest(ctx context.Context, client *http.Client,
|
|
url string, payload interface{}, headers map[string]string) error {
|
|
|
|
jsonData, err := json.Marshal(payload)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to marshal: %w", err)
|
|
}
|
|
|
|
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonData))
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create request: %w", err)
|
|
}
|
|
|
|
req.Header.Set("Content-Type", "application/json")
|
|
for k, v := range headers {
|
|
req.Header.Set(k, v)
|
|
}
|
|
|
|
resp, err := client.Do(req)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to send request: %w", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
|
return fmt.Errorf("API returned status: %d", resp.StatusCode)
|
|
}
|
|
return nil
|
|
}
|
|
```
|
|
|
|
**Result:** Single source of truth for HTTP logic
|
|
|
|
---
|
|
|
|
## More Involved Refactorings
|
|
|
|
### 7. Generic Notifier Registration (60 minutes)
|
|
**File:** `/Users/igodwin/Workspace/notifier/cmd/server/main.go` (lines 193-241)
|
|
|
|
Creates generic registration helper to reduce 50+ lines of repetitive code.
|
|
|
|
### 8. Generic Default Account Resolution (45 minutes)
|
|
**File:** `/Users/igodwin/Workspace/notifier/internal/config/config.go` (lines 346-380)
|
|
|
|
Extract generic helper to eliminate 35+ lines of repetitive default account lookup.
|
|
|
|
---
|
|
|
|
## Implementation Checklist
|
|
|
|
### Phase 1 (Recommended: Next Hour)
|
|
- [ ] Remove duplicate `convertDomainToProtoType` (15 min)
|
|
- [ ] Extract validation helper (30 min)
|
|
- [ ] Add error result helper (30 min)
|
|
- **Total: 75 minutes**
|
|
|
|
### Phase 2 (Recommended: Next Sprint)
|
|
- [ ] Refactor filter matching (40 min)
|
|
- [ ] Extract auth validation (50 min)
|
|
- [ ] Create HTTP helper (45 min)
|
|
- **Total: 135 minutes**
|
|
|
|
### Phase 3 (Nice to Have: Later)
|
|
- [ ] Generic registration function (60 min)
|
|
- [ ] Generic default resolution (45 min)
|
|
- **Total: 105 minutes**
|
|
|
|
---
|
|
|
|
## Testing Strategy
|
|
|
|
1. **Phase 1 refactorings:** Run existing unit tests - no behavior changes
|
|
2. **Phase 2 refactorings:** Add unit tests for new helper functions
|
|
3. **Phase 3 refactorings:** Comprehensive integration tests for registration flow
|
|
|
|
All changes maintain backward compatibility.
|
|
|
|
---
|
|
|
|
## Expected Benefits
|
|
|
|
| Metric | Phase 1 | Phase 1+2 | All Phases |
|
|
|--------|---------|-----------|-----------|
|
|
| Lines of duplication removed | ~60 | ~250 | ~380 |
|
|
| Number of duplicate patterns | 3 | 6 | 8 |
|
|
| Estimated maintenance effort reduction | 15% | 40% | 60% |
|
|
|
|
---
|
|
|
|
## Files to Review After Refactoring
|
|
|
|
1. All notifier implementations (`internal/notifier/`)
|
|
2. Service filtering logic (`internal/service/service.go`)
|
|
3. Auth middleware files (`internal/auth/`)
|
|
4. Main server initialization (`cmd/server/main.go`)
|
|
5. Config file (`internal/config/config.go`)
|