Add keywords
This commit is contained in:
parent
bea1e67b19
commit
626085ed67
6 changed files with 198 additions and 3 deletions
|
|
@ -81,6 +81,7 @@ public function getStatusAttribute(): string
|
|||
public function channels(): BelongsToMany
|
||||
{
|
||||
return $this->belongsToMany(PlatformChannel::class, 'feed_platform_channels')
|
||||
->using(FeedPlatformChannel::class)
|
||||
->withPivot(['is_active', 'priority', 'filters'])
|
||||
->withTimestamps();
|
||||
}
|
||||
|
|
|
|||
53
app/Models/FeedPlatformChannel.php
Normal file
53
app/Models/FeedPlatformChannel.php
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Database\Eloquent\Relations\Pivot;
|
||||
use Illuminate\Support\Carbon;
|
||||
|
||||
/**
|
||||
* @property int $feed_id
|
||||
* @property int $platform_channel_id
|
||||
* @property bool $is_active
|
||||
* @property int $priority
|
||||
* @property array $filters
|
||||
* @property Carbon $created_at
|
||||
* @property Carbon $updated_at
|
||||
*/
|
||||
class FeedPlatformChannel extends Pivot
|
||||
{
|
||||
protected $table = 'feed_platform_channels';
|
||||
|
||||
public $incrementing = false;
|
||||
|
||||
protected $fillable = [
|
||||
'feed_id',
|
||||
'platform_channel_id',
|
||||
'is_active',
|
||||
'priority',
|
||||
'filters'
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'is_active' => 'boolean',
|
||||
'filters' => 'array'
|
||||
];
|
||||
|
||||
public function feed(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Feed::class);
|
||||
}
|
||||
|
||||
public function platformChannel(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(PlatformChannel::class);
|
||||
}
|
||||
|
||||
public function keywords(): HasMany
|
||||
{
|
||||
return $this->hasMany(Keyword::class, 'feed_id', 'feed_id')
|
||||
->where('platform_channel_id', $this->platform_channel_id);
|
||||
}
|
||||
}
|
||||
43
app/Models/Keyword.php
Normal file
43
app/Models/Keyword.php
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Support\Carbon;
|
||||
|
||||
/**
|
||||
* @property int $id
|
||||
* @property int $feed_id
|
||||
* @property Feed $feed
|
||||
* @property int $platform_channel_id
|
||||
* @property PlatformChannel $platformChannel
|
||||
* @property string $keyword
|
||||
* @property bool $is_active
|
||||
* @property Carbon $created_at
|
||||
* @property Carbon $updated_at
|
||||
*/
|
||||
class Keyword extends Model
|
||||
{
|
||||
protected $fillable = [
|
||||
'feed_id',
|
||||
'platform_channel_id',
|
||||
'keyword',
|
||||
'is_active'
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'is_active' => 'boolean'
|
||||
];
|
||||
|
||||
public function feed(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Feed::class);
|
||||
}
|
||||
|
||||
public function platformChannel(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(PlatformChannel::class);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -58,6 +58,7 @@ public function getFullNameAttribute(): string
|
|||
public function feeds(): BelongsToMany
|
||||
{
|
||||
return $this->belongsToMany(Feed::class, 'feed_platform_channels')
|
||||
->using(FeedPlatformChannel::class)
|
||||
->withPivot(['is_active', 'priority', 'filters'])
|
||||
->withTimestamps();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,33 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('keywords', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('feed_id')->constrained()->onDelete('cascade');
|
||||
$table->foreignId('platform_channel_id')->constrained()->onDelete('cascade');
|
||||
$table->string('keyword');
|
||||
$table->boolean('is_active')->default(true);
|
||||
$table->timestamps();
|
||||
|
||||
$table->unique(['feed_id', 'platform_channel_id', 'keyword']);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('keywords');
|
||||
}
|
||||
};
|
||||
|
|
@ -75,13 +75,43 @@ class="mt-1 block w-full border-gray-300 rounded-md shadow-sm focus:ring-indigo-
|
|||
</div>
|
||||
|
||||
<div>
|
||||
<label for="filters" class="block text-sm font-medium text-gray-700">Filters (Optional)</label>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-3">Keywords (Optional)</label>
|
||||
<div id="keywords-container" class="space-y-2">
|
||||
<div class="keyword-input-group flex items-center space-x-2">
|
||||
<input type="text"
|
||||
name="keywords[]"
|
||||
class="flex-1 border-gray-300 rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm"
|
||||
placeholder="Enter keyword">
|
||||
<button type="button"
|
||||
class="remove-keyword bg-red-500 hover:bg-red-600 text-white px-3 py-2 rounded-md text-sm"
|
||||
onclick="removeKeyword(this)">
|
||||
Remove
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<button type="button"
|
||||
id="add-keyword"
|
||||
class="mt-2 bg-green-500 hover:bg-green-600 text-white px-3 py-2 rounded-md text-sm"
|
||||
onclick="addKeyword()">
|
||||
Add Keyword
|
||||
</button>
|
||||
<p class="mt-1 text-sm text-gray-500">Articles will be filtered to only include content matching these keywords</p>
|
||||
@error('keywords')
|
||||
<p class="mt-2 text-sm text-red-600">{{ $message }}</p>
|
||||
@enderror
|
||||
@error('keywords.*')
|
||||
<p class="mt-2 text-sm text-red-600">{{ $message }}</p>
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="filters" class="block text-sm font-medium text-gray-700">Other Filters (Optional)</label>
|
||||
<textarea name="filters"
|
||||
id="filters"
|
||||
rows="4"
|
||||
class="mt-1 block w-full border-gray-300 rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm @error('filters') border-red-300 @enderror"
|
||||
placeholder='{"keywords": ["technology", "AI"], "exclude_keywords": ["sports"]}'>{{ old('filters') }}</textarea>
|
||||
<p class="mt-1 text-sm text-gray-500">JSON format for content filtering rules</p>
|
||||
placeholder='{"exclude_keywords": ["sports"], "min_length": 100}'>{{ old('filters') }}</textarea>
|
||||
<p class="mt-1 text-sm text-gray-500">JSON format for additional content filtering rules</p>
|
||||
@error('filters')
|
||||
<p class="mt-2 text-sm text-red-600">{{ $message }}</p>
|
||||
@enderror
|
||||
|
|
@ -124,4 +154,38 @@ class="mt-1 block w-full border-gray-300 rounded-md shadow-sm focus:ring-indigo-
|
|||
@endif
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function addKeyword() {
|
||||
const container = document.getElementById('keywords-container');
|
||||
const newGroup = document.createElement('div');
|
||||
newGroup.className = 'keyword-input-group flex items-center space-x-2';
|
||||
newGroup.innerHTML = `
|
||||
<input type="text"
|
||||
name="keywords[]"
|
||||
class="flex-1 border-gray-300 rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm"
|
||||
placeholder="Enter keyword">
|
||||
<button type="button"
|
||||
class="remove-keyword bg-red-500 hover:bg-red-600 text-white px-3 py-2 rounded-md text-sm"
|
||||
onclick="removeKeyword(this)">
|
||||
Remove
|
||||
</button>
|
||||
`;
|
||||
container.appendChild(newGroup);
|
||||
}
|
||||
|
||||
function removeKeyword(button) {
|
||||
const container = document.getElementById('keywords-container');
|
||||
const groups = container.querySelectorAll('.keyword-input-group');
|
||||
|
||||
// Don't remove if it's the last remaining input
|
||||
if (groups.length > 1) {
|
||||
button.parentElement.remove();
|
||||
} else {
|
||||
// Clear the input value instead of removing the field
|
||||
const input = button.parentElement.querySelector('input');
|
||||
input.value = '';
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@endsection
|
||||
Loading…
Reference in a new issue