Proje dosyaları eklendi

This commit is contained in:
2026-08-29 15:53:53 +03:00
commit d3313400ca
440 changed files with 24827 additions and 0 deletions
+3
View File
@@ -0,0 +1,3 @@
<?php
namespace App\Models;use Illuminate\Database\Eloquent\Model;
class CompetitorAudit extends Model{protected$guarded=[];protected function casts():array{return['before'=>'array','after'=>'array'];}}
+17
View File
@@ -0,0 +1,17 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class EarnApiAccessLog extends Model
{
public $timestamps = false;
protected $guarded = [];
protected function casts(): array
{
return ['occurred_at' => 'datetime', 'created_at' => 'datetime'];
}
}
+45
View File
@@ -0,0 +1,45 @@
<?php
namespace App\Models;
use App\Enums\EarnApiTokenStatus;
use Illuminate\Database\Eloquent\SoftDeletes;
use Laravel\Sanctum\PersonalAccessToken;
class EarnApiToken extends PersonalAccessToken
{
use SoftDeletes;
protected $table = 'personal_access_tokens';
protected function casts(): array
{
return array_merge(parent::casts(), [
'is_active' => 'boolean', 'revoked_at' => 'datetime', 'deleted_at' => 'datetime',
'device_bound_at' => 'datetime', 'last_seen_at' => 'datetime', 'device_mismatch_seen' => 'boolean',
]);
}
public function status(): EarnApiTokenStatus
{
if ($this->revoked_at !== null) {
return EarnApiTokenStatus::Revoked;
}
if ($this->expires_at !== null && $this->expires_at->isPast()) {
return EarnApiTokenStatus::Expired;
}
if (! $this->is_active) {
return EarnApiTokenStatus::Inactive;
}
return EarnApiTokenStatus::Active;
}
public function canAccessTasks(): bool
{
return $this->status() === EarnApiTokenStatus::Active
&& $this->can('tasks:read')
&& $this->tokenable instanceof EarnMember
&& $this->tokenable->canUseApi();
}
}
+17
View File
@@ -0,0 +1,17 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class EarnAudit extends Model
{
public $timestamps = false;
protected $guarded = [];
protected function casts(): array
{
return ['metadata' => 'array', 'created_at' => 'datetime'];
}
}
+22
View File
@@ -0,0 +1,22 @@
<?php
namespace App\Models;
use App\Enums\EarnLegalDocumentType;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class EarnLegalAcceptance extends Model
{
protected $guarded = [];
protected function casts(): array
{
return ['document_type' => EarnLegalDocumentType::class, 'accepted_at' => 'datetime'];
}
public function member(): BelongsTo
{
return $this->belongsTo(EarnMember::class, 'earn_member_id');
}
}
+60
View File
@@ -0,0 +1,60 @@
<?php
namespace App\Models;
use App\Enums\EarnMemberStatus;
use App\Notifications\EarnVerifyEmail;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Attributes\Fillable;
use Illuminate\Database\Eloquent\Attributes\Hidden;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Illuminate\Support\Str;
use Laravel\Sanctum\HasApiTokens;
#[Fillable(['name', 'email', 'password', 'status', 'email_verified_at', 'last_login_at', 'last_login_ip'])]
#[Hidden(['password', 'remember_token'])]
class EarnMember extends Authenticatable implements MustVerifyEmail
{
use HasApiTokens, HasFactory, Notifiable, SoftDeletes;
protected static function booted(): void
{
static::creating(function (self $member): void {
$member->ulid ??= (string) Str::ulid();
});
}
protected function casts(): array
{
return [
'password' => 'hashed',
'status' => EarnMemberStatus::class,
'email_verified_at' => 'datetime',
'last_login_at' => 'datetime',
];
}
public function audits(): HasMany
{
return $this->hasMany(EarnAudit::class);
}
public function legalAcceptances(): HasMany
{
return $this->hasMany(EarnLegalAcceptance::class);
}
public function canUseApi(): bool
{
return ! $this->trashed() && $this->status === EarnMemberStatus::Active && $this->hasVerifiedEmail();
}
public function sendEmailVerificationNotification(): void
{
$this->notify(new EarnVerifyEmail);
}
}
+32
View File
@@ -0,0 +1,32 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasOne;
class EarnTaskAssignment extends Model
{
protected $guarded = [];
/**
* Atamanın gerçek müşteri sahibini döndürür.
*
* @author Gökhan Engin - İletişim : https://bit.ly/YazılımUzmanı
*/
public function user(): BelongsTo
{
return $this->belongsTo(User::class);
}
/**
* Atama için kabul edilmiş tek idempotent sonucu döndürür.
*
* @author Gökhan Engin - İletişim : https://bit.ly/YazılımUzmanı
*/
public function result(): HasOne
{
return $this->hasOne(EarnTaskResult::class);
}
}
+43
View File
@@ -0,0 +1,43 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class EarnTaskResult extends Model
{
protected $guarded = [];
/**
* Sonucun sunucu tarafından doğrulanmış görev atamasını döndürür.
*
* @author Gökhan Engin - İletişim : https://bit.ly/YazılımUzmanı
*/
public function assignment(): BelongsTo
{
return $this->belongsTo(EarnTaskAssignment::class, 'earn_task_assignment_id');
}
/**
* Sonucun ait olduğu normal müşteri kullanıcısını döndürür.
*
* @author Gökhan Engin - İletişim : https://bit.ly/YazılımUzmanı
*/
public function user(): BelongsTo
{
return $this->belongsTo(User::class);
}
/**
* Sonuç alanlarını güvenli PHP türlerine dönüştürür.
*
* @return array<string, string>
*
* @author Gökhan Engin - İletişim : https://bit.ly/YazılımUzmanı
*/
protected function casts(): array
{
return ['completed' => 'boolean', 'submitted_at' => 'datetime'];
}
}
+4
View File
@@ -0,0 +1,4 @@
<?php
namespace App\Models;
use App\Enums\{CheckFrequency,KeywordDevice,KeywordStatus,SearchEngine};use Illuminate\Database\Eloquent\{Builder,Model};use Illuminate\Database\Eloquent\Concerns\HasUlids;use Illuminate\Database\Eloquent\Factories\HasFactory;use Illuminate\Database\Eloquent\Relations\{BelongsTo,HasMany};
class Keyword extends Model {use HasFactory,HasUlids;protected $fillable=['phrase','normalized_phrase','search_engine','country_code','language_code','device'];protected function casts():array{return ['search_engine'=>SearchEngine::class,'device'=>KeywordDevice::class,'status'=>KeywordStatus::class,'archived_at'=>'datetime','tracking_enabled'=>'boolean','check_frequency'=>CheckFrequency::class,'next_check_at'=>'datetime','last_queued_at'=>'datetime'];}public function uniqueIds():array{return ['ulid'];}public function getRouteKeyName():string{return 'ulid';}public function project():BelongsTo{return $this->belongsTo(Project::class);}public function serpChecks():HasMany{return $this->hasMany(SerpCheck::class);}public function scopeForProject(Builder $q,Project|int $p):Builder{return $q->where('project_id',$p instanceof Project?$p->id:$p);}public function scopeActive(Builder $q):Builder{return $q->where('status',KeywordStatus::Active)->whereNull('archived_at');}public function scopeArchived(Builder $q):Builder{return $q->where('status',KeywordStatus::Archived)->whereNotNull('archived_at');}public function archive():bool{return $this->forceFill(['status'=>KeywordStatus::Archived,'archived_at'=>now()])->save();}public function restoreKeyword():bool{return $this->forceFill(['status'=>KeywordStatus::Active,'archived_at'=>null])->save();}}
+3
View File
@@ -0,0 +1,3 @@
<?php
namespace App\Models;use Illuminate\Database\Eloquent\Model;
class KeywordAudit extends Model{protected $guarded=[];protected function casts():array{return ['before'=>'array','after'=>'array'];}}
+12
View File
@@ -0,0 +1,12 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
class Permission extends Model
{
protected $fillable = ['name', 'label'];
public function roles(): BelongsToMany { return $this->belongsToMany(Role::class)->withTimestamps(); }
}
+3
View File
@@ -0,0 +1,3 @@
<?php
namespace App\Models;use Illuminate\Database\Eloquent\Model;
class ProfileAudit extends Model{protected$guarded=[];protected function casts():array{return['before'=>'array','after'=>'array'];}}
+46
View File
@@ -0,0 +1,46 @@
<?php
namespace App\Models;
use App\Enums\ProjectStatus;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Concerns\HasUlids;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
class Project extends Model
{
use HasFactory, HasUlids;
protected $fillable = ['name', 'domain', 'url'];
protected function casts(): array
{
return ['status' => ProjectStatus::class, 'archived_at' => 'datetime'];
}
public function uniqueIds(): array { return ['ulid']; }
public function getRouteKeyName(): string { return 'ulid'; }
public function user(): BelongsTo { return $this->belongsTo(User::class); }
public function owner(): BelongsTo { return $this->user(); }
public function keywords(): HasMany { return $this->hasMany(Keyword::class); }
public function competitors(): HasMany { return $this->hasMany(ProjectCompetitor::class); }
public function scopeOwnedBy(Builder $query, User|int $owner): Builder
{
return $query->where('user_id', $owner instanceof User ? $owner->getKey() : $owner);
}
public function scopeActive(Builder $query): Builder
{
return $query->where('status', ProjectStatus::Active)->whereNull('archived_at');
}
public function archive(): bool
{
return $this->forceFill(['status' => ProjectStatus::Archived, 'archived_at' => now()])->save();
}
public function restoreProject(): bool
{
return $this->forceFill(['status' => ProjectStatus::Active, 'archived_at' => null])->save();
}
}
+3
View File
@@ -0,0 +1,3 @@
<?php
namespace App\Models;use Illuminate\Database\Eloquent\{Builder,Model};use Illuminate\Database\Eloquent\Concerns\HasUlids;use Illuminate\Database\Eloquent\Relations\BelongsTo;
class ProjectCompetitor extends Model{use HasUlids;protected$fillable=['name','domain','normalized_domain','created_by'];protected function casts():array{return['archived_at'=>'datetime'];}public function uniqueIds():array{return['ulid'];}public function getRouteKeyName():string{return'ulid';}public function project():BelongsTo{return$this->belongsTo(Project::class);}public function creator():BelongsTo{return$this->belongsTo(User::class,'created_by');}public function scopeActive(Builder$q):Builder{return$q->where('status','active')->whereNull('archived_at');}}
+14
View File
@@ -0,0 +1,14 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
class Role extends Model
{
protected $fillable = ['name', 'label'];
public function permissions(): BelongsToMany { return $this->belongsToMany(Permission::class)->withTimestamps(); }
public function users(): BelongsToMany { return $this->belongsToMany(User::class)->withTimestamps(); }
}
+8
View File
@@ -0,0 +1,8 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class RoleAssignmentAudit extends Model
{
protected $guarded = [];
protected function casts(): array { return ['previous_roles'=>'array','new_roles'=>'array','added_roles'=>'array','removed_roles'=>'array']; }
}
+15
View File
@@ -0,0 +1,15 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class SerpBudgetEntry extends Model
{
protected $guarded = [];
protected function casts(): array
{
return ['reserved_at' => 'datetime', 'reconciled_at' => 'datetime'];
}
}
+72
View File
@@ -0,0 +1,72 @@
<?php
namespace App\Models;
use App\Enums\KeywordDevice;
use App\Enums\SearchEngine;
use App\Enums\SerpCheckStatus;
use Illuminate\Database\Eloquent\Concerns\HasUlids;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use LogicException;
class SerpCheck extends Model
{
use HasFactory,HasUlids;
protected $guarded = ['id', 'keyword_id', 'ulid'];
protected function casts(): array
{
return ['status' => SerpCheckStatus::class, 'search_engine' => SearchEngine::class, 'device' => KeywordDevice::class, 'checked_at' => 'datetime', 'started_at' => 'datetime', 'completed_at' => 'datetime', 'failed_at' => 'datetime', 'provider_task_posted_at' => 'datetime', 'provider_last_polled_at' => 'datetime', 'provider_next_poll_at' => 'datetime', 'provider_cost' => 'decimal:6'];
}
public function uniqueIds(): array
{
return ['ulid'];
}
public function getRouteKeyName(): string
{
return 'ulid';
}
public function keyword(): BelongsTo
{
return $this->belongsTo(Keyword::class);
}
public function results(): HasMany
{
return $this->hasMany(SerpResult::class);
}
public function begin(): bool
{
if ($this->status !== SerpCheckStatus::Pending) {
return false;
}
return $this->forceFill(['status' => SerpCheckStatus::Running, 'active_slot' => 'active', 'started_at' => now()])->save();
}
public function complete(array $data): bool
{
if ($this->status !== SerpCheckStatus::Running) {
throw new LogicException('Invalid check transition.');
}
return $this->forceFill($data + ['status' => SerpCheckStatus::Completed, 'active_slot' => null, 'checked_at' => now(), 'completed_at' => now(), 'failed_at' => null, 'error_code' => null, 'error_message' => null])->save();
}
public function fail(string $code, string $message): bool
{
if (! in_array($this->status, [SerpCheckStatus::Pending, SerpCheckStatus::Running], true)) {
return false;
}
return $this->forceFill(['status' => SerpCheckStatus::Failed, 'active_slot' => null, 'failed_at' => now(), 'error_code' => $code, 'error_message' => $message])->save();
}
}
+15
View File
@@ -0,0 +1,15 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class SerpOperationAudit extends Model
{
protected $guarded = [];
protected function casts(): array
{
return ['context' => 'array'];
}
}
+15
View File
@@ -0,0 +1,15 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class SerpOperationState extends Model
{
protected $guarded = [];
protected function casts(): array
{
return ['emergency_stopped_at' => 'datetime', 'last_health_at' => 'datetime', 'last_provider_success_at' => 'datetime', 'ramp_started_at' => 'datetime'];
}
}
+6
View File
@@ -0,0 +1,6 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class SerpResult extends Model { use HasFactory; protected static function booted():void{static::saving(function(self $result):void{if($result->position<1)throw new \InvalidArgumentException('Position pozitif olmalıdır.');});} protected $guarded=[]; protected function casts():array{return ['is_project_domain'=>'boolean'];} public function serpCheck():BelongsTo{return $this->belongsTo(SerpCheck::class);} }
+126
View File
@@ -0,0 +1,126 @@
<?php
namespace App\Models;
use App\Enums\RoleName;
use App\Enums\UserStatus;
use Database\Factories\UserFactory;
use Filament\Models\Contracts\FilamentUser;
use Filament\Panel;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Attributes\Fillable;
use Illuminate\Database\Eloquent\Attributes\Hidden;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\HasOne;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laravel\Sanctum\HasApiTokens;
#[Fillable(['name', 'email', 'password', 'locale', 'status'])]
#[Hidden(['password', 'remember_token'])]
class User extends Authenticatable implements FilamentUser, MustVerifyEmail
{
private const ADMIN_PORTAL_ROLES = [RoleName::SuperAdmin, RoleName::Admin];
/** @use HasFactory<UserFactory> */
use HasApiTokens, HasFactory, Notifiable, SoftDeletes;
/**
* Kullanıcı alanlarına uygulanacak güvenli tür dönüşümlerini tanımlar.
*
* @return array<string, string>
*
* @author Gökhan Engin - İletişim : https://bit.ly/YazılımUzmanı
*/
protected function casts(): array
{
return [
'email_verified_at' => 'datetime',
'last_login_at' => 'datetime',
'password' => 'hashed',
'status' => UserStatus::class,
];
}
public function roles(): BelongsToMany
{
return $this->belongsToMany(Role::class)->withTimestamps()
->orderByRaw("CASE roles.name WHEN 'super_admin' THEN 1 WHEN 'admin' THEN 2 WHEN 'support' THEN 3 WHEN 'customer' THEN 4 ELSE 5 END");
}
public function projects(): HasMany
{
return $this->hasMany(Project::class);
}
public function setting(): HasOne
{
return $this->hasOne(UserSetting::class);
}
/**
* Müşteriye sunucu tarafında bağlanmış görev sonuçlarını döndürür.
*
* @author Gökhan Engin - İletişim : https://bit.ly/YazılımUzmanı
*/
public function earnTaskResults(): HasMany
{
return $this->hasMany(EarnTaskResult::class);
}
public function hasRole(RoleName|string $role): bool
{
$name = $role instanceof RoleName ? $role->value : $role;
return $this->roles()->where('name', $name)->exists();
}
public function hasPermission(string $permission): bool
{
if ($this->hasRole(RoleName::SuperAdmin)) {
return true;
}
return $this->roles()->whereHas('permissions', fn ($query) => $query->where('name', $permission))->exists();
}
public function isActive(): bool
{
return $this->status === UserStatus::Active;
}
public function hasManagementRole(): bool
{
return $this->roles()->whereIn('name', array_map(
static fn (RoleName $role): string => $role->value,
self::ADMIN_PORTAL_ROLES,
))->exists();
}
/**
* Kullanıcının diğer rollerinden bağımsız olarak müşteri paneline erişimini denetler.
*
* @return bool Aktif kullanıcı customer rolüne sahipse true.
*
* @author Gökhan Engin - İletişim : https://bit.ly/YazılımUzmanı
*/
public function canAccessCustomerPortal(): bool
{
return ! $this->trashed() && $this->isActive()
&& $this->hasRole(RoleName::Customer);
}
public function canAccessAdminPortal(): bool
{
return ! $this->trashed() && $this->isActive() && $this->hasVerifiedEmail()
&& $this->hasManagementRole() && $this->hasPermission('admin.access');
}
public function canAccessPanel(Panel $panel): bool
{
return $panel->getId() === 'admin' && $this->canAccessAdminPortal();
}
}
+3
View File
@@ -0,0 +1,3 @@
<?php
namespace App\Models;use Illuminate\Database\Eloquent\Model;use Illuminate\Database\Eloquent\Relations\BelongsTo;
class UserSetting extends Model{protected$fillable=['theme','timezone','date_format','time_format','default_report_range','default_project_id','per_page','in_app_notifications','reduced_motion'];protected function casts():array{return['in_app_notifications'=>'boolean','reduced_motion'=>'boolean'];}public function user():BelongsTo{return$this->belongsTo(User::class);}public static function defaults():array{return['theme'=>'system','timezone'=>'Europe/Istanbul','date_format'=>'DD.MM.YYYY','time_format'=>'24','default_report_range'=>'30_days','default_project_id'=>null,'per_page'=>25,'in_app_notifications'=>true,'reduced_motion'=>false];}}