feat(word-filter): enhance WordFilterSeeder with demo and random filter creation

- Added methods to create specific demo filters for profanity, spam, and inappropriate content.
- Implemented random filter generation using factories for diverse testing scenarios.
- Updated documentation to reflect the new functionality and usage of the WordFilterSeeder.
This commit is contained in:
Yury Pikhtarev 2025-07-06 00:27:03 +02:00
commit eef4edc5fe
No known key found for this signature in database
9 changed files with 392 additions and 41 deletions

View file

@ -2,6 +2,7 @@
namespace Database\Seeders;
use App\Models\WordFilter;
use Illuminate\Database\Seeder;
class WordFilterSeeder extends Seeder
@ -11,6 +12,363 @@ class WordFilterSeeder extends Seeder
*/
public function run(): void
{
//
// This seeder adds filters without removing existing ones
// Demo filters use updateOrCreate to avoid duplicates
// First, create some specific examples for demonstration
$this->createDemoFilters();
// Then, generate random filters using the factory
$this->createRandomFilters();
}
/**
* Create specific demo filters with realistic examples.
*/
protected function createDemoFilters(): void
{
// Replace type filters - obscure profanity
WordFilter::updateOrCreate(
['pattern' => 'badword'],
[
'replacement' => '******',
'filter_type' => 'replace',
'pattern_type' => 'exact',
'severity' => 'high',
'is_active' => true,
'case_sensitive' => false,
'applies_to' => ['posts', 'private_messages', 'signatures'],
'notes' => 'Common profanity - exact match',
]
);
WordFilter::updateOrCreate(
['pattern' => 'damn'],
[
'replacement' => 'd***',
'filter_type' => 'replace',
'pattern_type' => 'exact',
'severity' => 'low',
'is_active' => true,
'case_sensitive' => false,
'applies_to' => ['posts'],
'notes' => 'Mild profanity',
]
);
// Wildcard patterns
WordFilter::updateOrCreate(
['pattern' => '*spam*'],
[
'replacement' => '[removed]',
'filter_type' => 'replace',
'pattern_type' => 'wildcard',
'severity' => 'medium',
'is_active' => true,
'case_sensitive' => false,
'applies_to' => ['posts', 'private_messages'],
'notes' => 'Spam-related content',
]
);
WordFilter::updateOrCreate(
['pattern' => 'hate*'],
[
'replacement' => '****',
'filter_type' => 'replace',
'pattern_type' => 'wildcard',
'severity' => 'high',
'is_active' => true,
'case_sensitive' => false,
'applies_to' => ['posts', 'private_messages', 'usernames'],
'notes' => 'Hate speech patterns',
]
);
// Regex patterns
WordFilter::updateOrCreate(
['pattern' => '/\\b(viagra|cialis|levitra)\\b/i'],
[
'replacement' => '[pharmaceutical]',
'filter_type' => 'replace',
'pattern_type' => 'regex',
'severity' => 'medium',
'is_active' => true,
'case_sensitive' => false,
'applies_to' => ['posts', 'private_messages', 'signatures'],
'notes' => 'Pharmaceutical spam',
]
);
WordFilter::updateOrCreate(
['pattern' => '/\\b\\d{3}-\\d{3}-\\d{4}\\b/'],
[
'replacement' => '[phone number]',
'filter_type' => 'replace',
'pattern_type' => 'regex',
'severity' => 'low',
'is_active' => true,
'case_sensitive' => false,
'applies_to' => ['posts', 'private_messages'],
'notes' => 'Phone number pattern (US format)',
]
);
// Block type filters - completely prevent posting
WordFilter::updateOrCreate(
['pattern' => 'blocked_domain.com'],
[
'replacement' => null,
'filter_type' => 'block',
'pattern_type' => 'exact',
'severity' => 'high',
'is_active' => true,
'case_sensitive' => false,
'applies_to' => ['posts', 'private_messages', 'signatures'],
'notes' => 'Known malicious domain',
]
);
WordFilter::updateOrCreate(
['pattern' => '*malware*'],
[
'replacement' => null,
'filter_type' => 'block',
'pattern_type' => 'wildcard',
'severity' => 'high',
'is_active' => true,
'case_sensitive' => false,
'applies_to' => ['posts', 'private_messages'],
'notes' => 'Malware-related content',
]
);
WordFilter::updateOrCreate(
['pattern' => '/\\b(torrent|magnet:|ed2k:)\\/\\/\\b/i'],
[
'replacement' => null,
'filter_type' => 'block',
'pattern_type' => 'regex',
'severity' => 'medium',
'is_active' => false, // Disabled by default for torrent sites
'case_sensitive' => false,
'applies_to' => ['posts'],
'notes' => 'Torrent/P2P links - disabled for torrent trackers',
]
);
// Moderate type filters - flag for review
WordFilter::updateOrCreate(
['pattern' => 'suspicious'],
[
'replacement' => null,
'filter_type' => 'moderate',
'pattern_type' => 'exact',
'severity' => 'low',
'is_active' => true,
'case_sensitive' => false,
'applies_to' => ['posts'],
'notes' => 'Flag for manual review',
]
);
WordFilter::updateOrCreate(
['pattern' => '*scam*'],
[
'replacement' => null,
'filter_type' => 'moderate',
'pattern_type' => 'wildcard',
'severity' => 'medium',
'is_active' => true,
'case_sensitive' => false,
'applies_to' => ['posts', 'private_messages'],
'notes' => 'Potential scam content',
]
);
WordFilter::updateOrCreate(
['pattern' => '/\\$\\d{3,}/i'],
[
'replacement' => null,
'filter_type' => 'moderate',
'pattern_type' => 'regex',
'severity' => 'low',
'is_active' => true,
'case_sensitive' => false,
'applies_to' => ['posts', 'private_messages'],
'notes' => 'Large dollar amounts - potential scam',
]
);
// Email masking
WordFilter::updateOrCreate(
['pattern' => '/([a-zA-Z0-9._%+-]+)@([a-zA-Z0-9.-]+\\.[a-zA-Z]{2,})/i'],
[
'replacement' => '[email protected]',
'filter_type' => 'replace',
'pattern_type' => 'regex',
'severity' => 'low',
'is_active' => true,
'case_sensitive' => false,
'applies_to' => ['posts'],
'notes' => 'Email address masking for privacy',
]
);
// URL shortener blocking
WordFilter::updateOrCreate(
['pattern' => '/\\b(bit\\.ly|tinyurl\\.com|goo\\.gl|t\\.co)\\/\\w+/i'],
[
'replacement' => '[shortened URL]',
'filter_type' => 'replace',
'pattern_type' => 'regex',
'severity' => 'medium',
'is_active' => true,
'case_sensitive' => false,
'applies_to' => ['posts', 'private_messages'],
'notes' => 'URL shorteners - potential security risk',
]
);
// Inappropriate username filter
WordFilter::updateOrCreate(
['pattern' => 'admin'],
[
'replacement' => null,
'filter_type' => 'block',
'pattern_type' => 'exact',
'severity' => 'high',
'is_active' => true,
'case_sensitive' => false,
'applies_to' => ['usernames'],
'notes' => 'Prevent impersonation of administrators',
]
);
WordFilter::updateOrCreate(
['pattern' => '*moderator*'],
[
'replacement' => null,
'filter_type' => 'block',
'pattern_type' => 'wildcard',
'severity' => 'high',
'is_active' => true,
'case_sensitive' => false,
'applies_to' => ['usernames'],
'notes' => 'Prevent impersonation of moderators',
]
);
// Case-sensitive filter example
WordFilter::updateOrCreate(
['pattern' => 'CEO'],
[
'replacement' => '[title]',
'filter_type' => 'replace',
'pattern_type' => 'exact',
'severity' => 'low',
'is_active' => true,
'case_sensitive' => true,
'applies_to' => ['posts'],
'notes' => 'Replace CEO when in all caps only',
]
);
// Multiple content type example
WordFilter::updateOrCreate(
['pattern' => 'test123'],
[
'replacement' => '[test]',
'filter_type' => 'replace',
'pattern_type' => 'exact',
'severity' => 'low',
'is_active' => false, // Inactive example
'case_sensitive' => false,
'applies_to' => ['posts', 'private_messages', 'usernames', 'signatures', 'profile_fields'],
'notes' => 'Test filter - currently disabled',
]
);
}
/**
* Create random filters using the factory.
*/
protected function createRandomFilters(): void
{
// Create 10 random replace filters
WordFilter::factory()
->count(10)
->create([
'filter_type' => 'replace',
]);
// Create 5 random block filters
WordFilter::factory()
->count(5)
->create([
'filter_type' => 'block',
'replacement' => null,
]);
// Create 5 random moderate filters
WordFilter::factory()
->count(5)
->create([
'filter_type' => 'moderate',
'replacement' => null,
]);
// Create some high severity filters
WordFilter::factory()
->count(5)
->create([
'severity' => 'high',
'is_active' => true,
]);
// Create some regex pattern filters
WordFilter::factory()
->count(5)
->create([
'pattern_type' => 'regex',
'pattern' => $this->generateRandomRegexPattern(),
]);
// Create some wildcard filters for spam detection
WordFilter::factory()
->count(5)
->create([
'pattern_type' => 'wildcard',
'pattern' => '*' . fake()->randomElement(['buy', 'cheap', 'free', 'click', 'download']) . '*',
'filter_type' => 'replace',
'replacement' => '[SPAM]',
'applies_to' => ['posts', 'private_messages'],
]);
// Create inactive filters for testing
WordFilter::factory()
->count(5)
->create([
'is_active' => false,
]);
}
/**
* Generate a random regex pattern for testing.
*/
protected function generateRandomRegexPattern(): string
{
$patterns = [
'/\\b\\d{3}-\\d{2}-\\d{4}\\b/', // SSN pattern
'/\\b[A-Z]{2}\\d{6}\\b/', // License plate pattern
'/\\b(https?:\\/\\/)?[\\w\\-]+(\\.[\\w\\-]+)+[/#?]?.*$/', // URL pattern
'/\\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,}\\b/i', // Email pattern
'/\\b\\d{4}[\\s-]?\\d{4}[\\s-]?\\d{4}[\\s-]?\\d{4}\\b/', // Credit card pattern
'/\\b(\\+?1[\\s-]?)?\\(?\\d{3}\\)?[\\s-]?\\d{3}[\\s-]?\\d{4}\\b/', // Phone pattern
'/\\$\\d+(\\.\\d{2})?\\b/', // Currency pattern
'/\\b[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}\\b/i', // UUID pattern
];
return fake()->randomElement($patterns);
}
}

View file

@ -1,5 +1,5 @@
---
title: Emoji Aliases API
title: Emoji Aliases
sidebar_position: 2
---
@ -9,7 +9,7 @@ The Emoji Aliases API allows you to manage alternative shortcodes for emojis, en
## Base URL
```http
```text
/api/emoji/aliases
```
@ -19,7 +19,7 @@ The Emoji Aliases API allows you to manage alternative shortcodes for emojis, en
Get a paginated list of emoji aliases with optional filtering and relationship loading.
```http
```text
GET /api/emoji/aliases
```
@ -124,7 +124,7 @@ curl -X GET "https://api.example.com/api/emoji/aliases?emoji_id=1&with_emoji=1"
Search emoji aliases using Laravel Scout for full-text search capabilities.
```http
```text
GET /api/emoji/aliases/search
```
@ -187,7 +187,7 @@ curl -X GET "https://api.example.com/api/emoji/aliases/search?q=hap&limit=5&with
Get a specific emoji alias by ID with full relationship data.
```http
```text
GET /api/emoji/aliases/{id}
```
@ -246,7 +246,7 @@ curl -X GET "https://api.example.com/api/emoji/aliases/1" \
Create a new alias for an existing emoji.
```http
```text
POST /api/emoji/aliases
```
@ -289,7 +289,7 @@ curl -X POST "https://api.example.com/api/emoji/aliases" \
Update an existing emoji alias.
```http
```text
PUT /api/emoji/aliases/{id}
PATCH /api/emoji/aliases/{id}
```
@ -338,7 +338,7 @@ curl -X PATCH "https://api.example.com/api/emoji/aliases/3" \
Delete an emoji alias. The associated emoji remains unchanged.
```http
```text
DELETE /api/emoji/aliases/{id}
```

View file

@ -1,5 +1,5 @@
---
title: Emoji Categories API
title: Emoji Categories
sidebar_position: 3
---
@ -9,7 +9,7 @@ The Emoji Categories API allows you to manage emoji categories used to organize
## Base URL
```http
```text
/api/emoji/categories
```
@ -19,7 +19,7 @@ The Emoji Categories API allows you to manage emoji categories used to organize
Get a list of all emoji categories.
```http
```text
GET /api/emoji/categories
```
@ -68,7 +68,7 @@ curl -X GET "https://api.example.com/api/emoji/categories?with_emojis=1" \
Get a specific emoji category by ID.
```http
```text
GET /api/emoji/categories/{id}
```
@ -133,7 +133,7 @@ curl -X GET "https://api.example.com/api/emoji/categories/1?with_aliases=1" \
Create a new emoji category.
```http
```text
POST /api/emoji/categories
```
@ -178,7 +178,7 @@ curl -X POST "https://api.example.com/api/emoji/categories" \
Update an existing emoji category.
```http
```text
PUT /api/emoji/categories/{id}
PATCH /api/emoji/categories/{id}
```
@ -229,7 +229,7 @@ curl -X PATCH "https://api.example.com/api/emoji/categories/3" \
Delete an emoji category. Associated emojis will have their category_id set to null.
```http
```text
DELETE /api/emoji/categories/{id}
```

View file

@ -1,5 +1,5 @@
---
title: Emoji API
title: Emoji
sidebar_position: 1
---
@ -9,7 +9,7 @@ The Emoji API allows you to manage individual emojis, including Unicode emojis,
## Base URL
```http
```text
/api/emoji/emojis
```
@ -19,7 +19,7 @@ The Emoji API allows you to manage individual emojis, including Unicode emojis,
Get a paginated list of emojis with optional filtering and relationship loading.
```http
```text
GET /api/emoji/emojis
```
@ -95,7 +95,7 @@ curl -X GET "https://api.example.com/api/emoji/emojis?category_id=1&with_aliases
Search emojis using Laravel Scout for full-text search capabilities.
```http
```text
GET /api/emoji/emojis/search
```
@ -151,7 +151,7 @@ curl -X GET "https://api.example.com/api/emoji/emojis/search?q=smile&limit=10&wi
Get a specific emoji by ID with full relationship data.
```http
```text
GET /api/emoji/emojis/{id}
```
@ -212,7 +212,7 @@ curl -X GET "https://api.example.com/api/emoji/emojis/1" \
Create a new emoji. Supports Unicode emojis, custom images, and CSS sprites.
```http
```text
POST /api/emoji/emojis
```
@ -316,7 +316,7 @@ curl -X POST "https://api.example.com/api/emoji/emojis" \
Update an existing emoji.
```http
```text
PUT /api/emoji/emojis/{id}
PATCH /api/emoji/emojis/{id}
```
@ -371,7 +371,7 @@ curl -X PATCH "https://api.example.com/api/emoji/emojis/25" \
Delete an emoji. Associated aliases will be automatically deleted.
```http
```text
DELETE /api/emoji/emojis/{id}
```

View file

@ -11,7 +11,7 @@ Welcome to the API documentation. This section covers all available REST API end
All API endpoints are prefixed with `/api/` and use the following base URL:
```http
```text
https://your-domain.com/api/
```
@ -130,7 +130,7 @@ List endpoints support pagination using query parameters:
Example:
```http
```text
GET /api/emoji/emojis?page=2&per_page=25
```
@ -153,7 +153,7 @@ Use these parameters to include related data:
Example:
```http
```text
GET /api/emoji/emojis?search=smile&with_category=1&with_aliases=1
```
@ -161,7 +161,7 @@ GET /api/emoji/emojis?search=smile&with_category=1&with_aliases=1
The current API version is v1. Future versions will be available at:
```http
```text
/api/v2/endpoint
```

View file

@ -1,3 +1,8 @@
---
title: Word Filters
sidebar_position: 2
---
# Word Filters API
The Word Filters API allows you to manage content filtering rules for your forum. Word filters can automatically replace, block, or moderate content based on configurable patterns.

View file

@ -4,10 +4,6 @@ use App\Models\Emoji;
use App\Models\EmojiAlias;
use App\Models\EmojiCategory;
beforeEach(function () {
config(['scout.driver' => 'collection']);
});
describe('Emoji Alias API Endpoints', function () {
test('can list aliases', function () {
$category = EmojiCategory::factory()->create();
@ -300,7 +296,7 @@ describe('Emoji Alias Search API', function () {
'alias' => ':sad:',
]);
$response = $this->getJson('/api/emoji/aliases/search?q=happy');
$response = $this->getJson('/api/emoji/aliases/search?q=happy&with_emoji=1');
$response->assertOk()
->assertJsonStructure([

View file

@ -4,10 +4,6 @@ use App\Models\Emoji;
use App\Models\EmojiAlias;
use App\Models\EmojiCategory;
beforeEach(function () {
config(['scout.driver' => 'collection']);
});
describe('Emoji API Endpoints', function () {
test('can list emojis', function () {
$category = EmojiCategory::factory()->create();

View file

@ -6,10 +6,6 @@ use Illuminate\Foundation\Testing\RefreshDatabase;
uses(RefreshDatabase::class);
beforeEach(function () {
config(['scout.driver' => 'collection']);
});
describe('Word Filter API', function () {
describe('Index', function () {
test('can list word filters', function () {