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
|
public function channels(): BelongsToMany
|
||||||
{
|
{
|
||||||
return $this->belongsToMany(PlatformChannel::class, 'feed_platform_channels')
|
return $this->belongsToMany(PlatformChannel::class, 'feed_platform_channels')
|
||||||
|
->using(FeedPlatformChannel::class)
|
||||||
->withPivot(['is_active', 'priority', 'filters'])
|
->withPivot(['is_active', 'priority', 'filters'])
|
||||||
->withTimestamps();
|
->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
|
public function feeds(): BelongsToMany
|
||||||
{
|
{
|
||||||
return $this->belongsToMany(Feed::class, 'feed_platform_channels')
|
return $this->belongsToMany(Feed::class, 'feed_platform_channels')
|
||||||
|
->using(FeedPlatformChannel::class)
|
||||||
->withPivot(['is_active', 'priority', 'filters'])
|
->withPivot(['is_active', 'priority', 'filters'])
|
||||||
->withTimestamps();
|
->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>
|
||||||
|
|
||||||
<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"
|
<textarea name="filters"
|
||||||
id="filters"
|
id="filters"
|
||||||
rows="4"
|
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"
|
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>
|
placeholder='{"exclude_keywords": ["sports"], "min_length": 100}'>{{ old('filters') }}</textarea>
|
||||||
<p class="mt-1 text-sm text-gray-500">JSON format for content filtering rules</p>
|
<p class="mt-1 text-sm text-gray-500">JSON format for additional content filtering rules</p>
|
||||||
@error('filters')
|
@error('filters')
|
||||||
<p class="mt-2 text-sm text-red-600">{{ $message }}</p>
|
<p class="mt-2 text-sm text-red-600">{{ $message }}</p>
|
||||||
@enderror
|
@enderror
|
||||||
|
|
@ -124,4 +154,38 @@ class="mt-1 block w-full border-gray-300 rounded-md shadow-sm focus:ring-indigo-
|
||||||
@endif
|
@endif
|
||||||
</div>
|
</div>
|
||||||
</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
|
@endsection
|
||||||
Loading…
Reference in a new issue