update
This commit is contained in:
46
app/Models/Achievement.php
Normal file
46
app/Models/Achievement.php
Normal file
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use App\Models\UserAchievement;
|
||||
|
||||
class Achievement extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'slug',
|
||||
'description',
|
||||
'icon',
|
||||
'xp_reward',
|
||||
'type',
|
||||
'condition_type',
|
||||
'condition_value',
|
||||
];
|
||||
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'xp_reward' => 'integer',
|
||||
'condition_value' => 'integer',
|
||||
];
|
||||
}
|
||||
|
||||
public function userAchievements(): HasMany
|
||||
{
|
||||
return $this->hasMany(UserAchievement::class, 'achievement_id');
|
||||
}
|
||||
|
||||
public function users(): BelongsToMany
|
||||
{
|
||||
return $this->belongsToMany(User::class, 'user_achievements', 'achievement_id', 'user_id')
|
||||
->withPivot('unlocked_at');
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,7 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
* Unified activity feed event.
|
||||
*
|
||||
* Types: upload | comment | favorite | award | follow
|
||||
* target_type: artwork | user
|
||||
* target_type: artwork | story | user
|
||||
*
|
||||
* @property int $id
|
||||
* @property int $actor_id
|
||||
@@ -54,6 +54,7 @@ class ActivityEvent extends Model
|
||||
const TYPE_FOLLOW = 'follow';
|
||||
|
||||
const TARGET_ARTWORK = 'artwork';
|
||||
const TARGET_STORY = 'story';
|
||||
const TARGET_USER = 'user';
|
||||
|
||||
// ── Relations ─────────────────────────────────────────────────────────────
|
||||
|
||||
88
app/Models/Country.php
Normal file
88
app/Models/Country.php
Normal file
@@ -0,0 +1,88 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
|
||||
final class Country extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = [
|
||||
'iso',
|
||||
'iso2',
|
||||
'iso3',
|
||||
'numeric_code',
|
||||
'name',
|
||||
'native',
|
||||
'phone',
|
||||
'continent',
|
||||
'capital',
|
||||
'currency',
|
||||
'languages',
|
||||
'name_common',
|
||||
'name_official',
|
||||
'region',
|
||||
'subregion',
|
||||
'flag_svg_url',
|
||||
'flag_png_url',
|
||||
'flag_emoji',
|
||||
'active',
|
||||
'sort_order',
|
||||
'is_featured',
|
||||
];
|
||||
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'active' => 'boolean',
|
||||
'is_featured' => 'boolean',
|
||||
'sort_order' => 'integer',
|
||||
];
|
||||
}
|
||||
|
||||
public function users(): HasMany
|
||||
{
|
||||
return $this->hasMany(User::class);
|
||||
}
|
||||
|
||||
public function scopeActive(Builder $query): Builder
|
||||
{
|
||||
return $query->where('active', true);
|
||||
}
|
||||
|
||||
public function scopeOrdered(Builder $query): Builder
|
||||
{
|
||||
return $query
|
||||
->orderByDesc('is_featured')
|
||||
->orderBy('sort_order')
|
||||
->orderBy('name_common');
|
||||
}
|
||||
|
||||
public function getFlagCssClassAttribute(): ?string
|
||||
{
|
||||
$iso2 = strtoupper((string) $this->iso2);
|
||||
|
||||
if (! preg_match('/^[A-Z]{2}$/', $iso2)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return 'fi fi-'.strtolower($iso2);
|
||||
}
|
||||
|
||||
public function getLocalFlagPathAttribute(): ?string
|
||||
{
|
||||
$iso2 = strtoupper((string) $this->iso2);
|
||||
|
||||
if (! preg_match('/^[A-Z]{2}$/', $iso2)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return '/gfx/flags/shiny/24/'.rawurlencode($iso2).'.png';
|
||||
}
|
||||
}
|
||||
91
app/Models/DashboardPreference.php
Normal file
91
app/Models/DashboardPreference.php
Normal file
@@ -0,0 +1,91 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
class DashboardPreference extends Model
|
||||
{
|
||||
public const MAX_PINNED_SPACES = 8;
|
||||
|
||||
/**
|
||||
* @var list<string>
|
||||
*/
|
||||
private const ALLOWED_PINNED_SPACES = [
|
||||
'/dashboard/profile',
|
||||
'/dashboard/notifications',
|
||||
'/dashboard/comments/received',
|
||||
'/dashboard/followers',
|
||||
'/dashboard/following',
|
||||
'/dashboard/favorites',
|
||||
'/dashboard/artworks',
|
||||
'/dashboard/gallery',
|
||||
'/dashboard/awards',
|
||||
'/creator/stories',
|
||||
'/studio',
|
||||
];
|
||||
|
||||
protected $table = 'dashboard_preferences';
|
||||
protected $primaryKey = 'user_id';
|
||||
public $incrementing = false;
|
||||
protected $keyType = 'int';
|
||||
|
||||
protected $fillable = [
|
||||
'user_id',
|
||||
'pinned_spaces',
|
||||
];
|
||||
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'pinned_spaces' => 'array',
|
||||
];
|
||||
}
|
||||
|
||||
public function user(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class, 'user_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int, mixed> $hrefs
|
||||
* @return list<string>
|
||||
*/
|
||||
public static function sanitizePinnedSpaces(array $hrefs): array
|
||||
{
|
||||
$allowed = array_fill_keys(self::ALLOWED_PINNED_SPACES, true);
|
||||
$sanitized = [];
|
||||
|
||||
foreach ($hrefs as $href) {
|
||||
if (! is_string($href) || ! isset($allowed[$href])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (in_array($href, $sanitized, true)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$sanitized[] = $href;
|
||||
|
||||
if (count($sanitized) >= self::MAX_PINNED_SPACES) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $sanitized;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<string>
|
||||
*/
|
||||
public static function pinnedSpacesForUser(User $user): array
|
||||
{
|
||||
$preference = static::query()->find($user->id);
|
||||
$spaces = $preference?->pinned_spaces;
|
||||
|
||||
return is_array($spaces) ? static::sanitizePinnedSpaces($spaces) : [];
|
||||
}
|
||||
}
|
||||
37
app/Models/Leaderboard.php
Normal file
37
app/Models/Leaderboard.php
Normal file
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class Leaderboard extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
public const TYPE_CREATOR = 'creator';
|
||||
public const TYPE_ARTWORK = 'artwork';
|
||||
public const TYPE_STORY = 'story';
|
||||
|
||||
public const PERIOD_DAILY = 'daily';
|
||||
public const PERIOD_WEEKLY = 'weekly';
|
||||
public const PERIOD_MONTHLY = 'monthly';
|
||||
public const PERIOD_ALL_TIME = 'all_time';
|
||||
|
||||
protected $fillable = [
|
||||
'type',
|
||||
'entity_id',
|
||||
'score',
|
||||
'period',
|
||||
];
|
||||
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'entity_id' => 'integer',
|
||||
'score' => 'float',
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,8 @@ declare(strict_types=1);
|
||||
namespace App\Models;
|
||||
|
||||
use App\Models\StoryLike;
|
||||
use App\Models\StoryBookmark;
|
||||
use App\Models\StoryComment;
|
||||
use App\Models\StoryView;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
@@ -101,6 +103,16 @@ class Story extends Model
|
||||
return $this->hasMany(StoryLike::class, 'story_id');
|
||||
}
|
||||
|
||||
public function comments(): HasMany
|
||||
{
|
||||
return $this->hasMany(StoryComment::class, 'story_id');
|
||||
}
|
||||
|
||||
public function bookmarks(): HasMany
|
||||
{
|
||||
return $this->hasMany(StoryBookmark::class, 'story_id');
|
||||
}
|
||||
|
||||
// ── Scopes ───────────────────────────────────────────────────────────
|
||||
|
||||
public function scopePublished($query)
|
||||
|
||||
36
app/Models/StoryBookmark.php
Normal file
36
app/Models/StoryBookmark.php
Normal file
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
class StoryBookmark extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = [
|
||||
'story_id',
|
||||
'user_id',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'story_id' => 'integer',
|
||||
'user_id' => 'integer',
|
||||
'created_at' => 'datetime',
|
||||
'updated_at' => 'datetime',
|
||||
];
|
||||
|
||||
public function story(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Story::class, 'story_id');
|
||||
}
|
||||
|
||||
public function user(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class, 'user_id');
|
||||
}
|
||||
}
|
||||
62
app/Models/StoryComment.php
Normal file
62
app/Models/StoryComment.php
Normal file
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
class StoryComment extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
use SoftDeletes;
|
||||
|
||||
protected $fillable = [
|
||||
'story_id',
|
||||
'user_id',
|
||||
'parent_id',
|
||||
'content',
|
||||
'raw_content',
|
||||
'rendered_content',
|
||||
'is_approved',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'story_id' => 'integer',
|
||||
'user_id' => 'integer',
|
||||
'parent_id' => 'integer',
|
||||
'is_approved' => 'boolean',
|
||||
'created_at' => 'datetime',
|
||||
'updated_at' => 'datetime',
|
||||
'deleted_at' => 'datetime',
|
||||
];
|
||||
|
||||
public function story(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Story::class, 'story_id');
|
||||
}
|
||||
|
||||
public function user(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class, 'user_id');
|
||||
}
|
||||
|
||||
public function parent(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(self::class, 'parent_id');
|
||||
}
|
||||
|
||||
public function replies(): HasMany
|
||||
{
|
||||
return $this->hasMany(self::class, 'parent_id')->orderBy('created_at');
|
||||
}
|
||||
|
||||
public function approvedReplies(): HasMany
|
||||
{
|
||||
return $this->replies()->where('is_approved', true)->whereNull('deleted_at')->with(['user.profile', 'approvedReplies']);
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ namespace App\Models;
|
||||
// use Illuminate\Contracts\Auth\MustVerifyEmail;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\HasOne;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use App\Models\SocialAccount;
|
||||
@@ -12,6 +13,9 @@ use App\Models\Conversation;
|
||||
use App\Models\ConversationParticipant;
|
||||
use App\Models\Message;
|
||||
use App\Models\Notification;
|
||||
use App\Models\Achievement;
|
||||
use App\Models\UserAchievement;
|
||||
use App\Models\UserXpLog;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||
use Illuminate\Notifications\Notifiable;
|
||||
@@ -50,6 +54,9 @@ class User extends Authenticatable
|
||||
'spam_reports',
|
||||
'approved_posts',
|
||||
'flagged_posts',
|
||||
'xp',
|
||||
'level',
|
||||
'rank',
|
||||
'password',
|
||||
'role',
|
||||
'allow_messages_from',
|
||||
@@ -88,6 +95,9 @@ class User extends Authenticatable
|
||||
'spam_reports' => 'integer',
|
||||
'approved_posts' => 'integer',
|
||||
'flagged_posts' => 'integer',
|
||||
'xp' => 'integer',
|
||||
'level' => 'integer',
|
||||
'rank' => 'string',
|
||||
'password' => 'hashed',
|
||||
'allow_messages_from' => 'string',
|
||||
];
|
||||
@@ -108,6 +118,16 @@ class User extends Authenticatable
|
||||
return $this->hasOne(UserProfile::class, 'user_id');
|
||||
}
|
||||
|
||||
public function dashboardPreference(): HasOne
|
||||
{
|
||||
return $this->hasOne(DashboardPreference::class, 'user_id');
|
||||
}
|
||||
|
||||
public function country(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Country::class);
|
||||
}
|
||||
|
||||
public function statistics(): HasOne
|
||||
{
|
||||
return $this->hasOne(UserStatistic::class, 'user_id');
|
||||
@@ -140,6 +160,22 @@ class User extends Authenticatable
|
||||
return $this->hasMany(ProfileComment::class, 'profile_user_id');
|
||||
}
|
||||
|
||||
public function xpLogs(): HasMany
|
||||
{
|
||||
return $this->hasMany(UserXpLog::class, 'user_id');
|
||||
}
|
||||
|
||||
public function userAchievements(): HasMany
|
||||
{
|
||||
return $this->hasMany(UserAchievement::class, 'user_id');
|
||||
}
|
||||
|
||||
public function achievements(): BelongsToMany
|
||||
{
|
||||
return $this->belongsToMany(Achievement::class, 'user_achievements', 'user_id', 'achievement_id')
|
||||
->withPivot('unlocked_at');
|
||||
}
|
||||
|
||||
// ── Messaging ────────────────────────────────────────────────────────────
|
||||
|
||||
public function conversations(): BelongsToMany
|
||||
|
||||
42
app/Models/UserAchievement.php
Normal file
42
app/Models/UserAchievement.php
Normal file
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use App\Models\Achievement;
|
||||
|
||||
class UserAchievement extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
public $timestamps = false;
|
||||
|
||||
protected $fillable = [
|
||||
'user_id',
|
||||
'achievement_id',
|
||||
'unlocked_at',
|
||||
];
|
||||
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'user_id' => 'integer',
|
||||
'achievement_id' => 'integer',
|
||||
'unlocked_at' => 'datetime',
|
||||
];
|
||||
}
|
||||
|
||||
public function user(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
|
||||
public function achievement(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Achievement::class, 'achievement_id');
|
||||
}
|
||||
}
|
||||
39
app/Models/UserXpLog.php
Normal file
39
app/Models/UserXpLog.php
Normal file
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
class UserXpLog extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
public $timestamps = false;
|
||||
|
||||
protected $fillable = [
|
||||
'user_id',
|
||||
'action',
|
||||
'xp',
|
||||
'reference_id',
|
||||
'created_at',
|
||||
];
|
||||
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'user_id' => 'integer',
|
||||
'xp' => 'integer',
|
||||
'reference_id' => 'integer',
|
||||
'created_at' => 'datetime',
|
||||
];
|
||||
}
|
||||
|
||||
public function user(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user