Commit workspace changes
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
<?php
|
||||
namespace App\Models;
|
||||
|
||||
use App\Models\ArtworkContributor;
|
||||
use App\Models\Group;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
@@ -26,6 +28,9 @@ class Artwork extends Model
|
||||
{
|
||||
use HasFactory, SoftDeletes, Searchable;
|
||||
|
||||
public const PUBLISHED_AS_USER = 'user';
|
||||
public const PUBLISHED_AS_GROUP = 'group';
|
||||
|
||||
public const VISIBILITY_PUBLIC = 'public';
|
||||
public const VISIBILITY_UNLISTED = 'unlisted';
|
||||
public const VISIBILITY_PRIVATE = 'private';
|
||||
@@ -34,6 +39,11 @@ class Artwork extends Model
|
||||
|
||||
protected $fillable = [
|
||||
'user_id',
|
||||
'group_id',
|
||||
'uploaded_by_user_id',
|
||||
'primary_author_user_id',
|
||||
'published_as_type',
|
||||
'published_as_id',
|
||||
'title',
|
||||
'slug',
|
||||
'description',
|
||||
@@ -81,6 +91,8 @@ class Artwork extends Model
|
||||
'is_approved' => 'boolean',
|
||||
'is_mature' => 'boolean',
|
||||
'published_at' => 'datetime',
|
||||
'published_as_type' => 'string',
|
||||
'published_as_id' => 'integer',
|
||||
'publish_at' => 'datetime',
|
||||
'clip_tags_json' => 'array',
|
||||
'yolo_objects_json' => 'array',
|
||||
@@ -167,6 +179,51 @@ class Artwork extends Model
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
|
||||
public function group(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Group::class);
|
||||
}
|
||||
|
||||
public function uploadedBy(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class, 'uploaded_by_user_id');
|
||||
}
|
||||
|
||||
public function primaryAuthor(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class, 'primary_author_user_id');
|
||||
}
|
||||
|
||||
public function contributors(): HasMany
|
||||
{
|
||||
return $this->hasMany(ArtworkContributor::class)->orderBy('sort_order');
|
||||
}
|
||||
|
||||
public function isPublishedByGroup(): bool
|
||||
{
|
||||
return $this->publishedAsType() === self::PUBLISHED_AS_GROUP;
|
||||
}
|
||||
|
||||
public function publishedAsType(): string
|
||||
{
|
||||
if (in_array($this->published_as_type, [self::PUBLISHED_AS_USER, self::PUBLISHED_AS_GROUP], true)) {
|
||||
return (string) $this->published_as_type;
|
||||
}
|
||||
|
||||
return (int) ($this->group_id ?? 0) > 0 ? self::PUBLISHED_AS_GROUP : self::PUBLISHED_AS_USER;
|
||||
}
|
||||
|
||||
public function publishedAsId(): int
|
||||
{
|
||||
if ((int) ($this->published_as_id ?? 0) > 0) {
|
||||
return (int) $this->published_as_id;
|
||||
}
|
||||
|
||||
return $this->publishedAsType() === self::PUBLISHED_AS_GROUP
|
||||
? (int) ($this->group_id ?? 0)
|
||||
: (int) $this->user_id;
|
||||
}
|
||||
|
||||
public function translations(): HasMany
|
||||
{
|
||||
return $this->hasMany(ArtworkTranslation::class);
|
||||
@@ -268,7 +325,7 @@ class Artwork extends Model
|
||||
*/
|
||||
public function toSearchableArray(): array
|
||||
{
|
||||
$this->loadMissing(['user', 'tags', 'categories.contentType', 'stats', 'awardStat']);
|
||||
$this->loadMissing(['user', 'group', 'tags', 'categories.contentType', 'stats', 'awardStat']);
|
||||
|
||||
$stat = $this->stats;
|
||||
$awardStat = $this->awardStat;
|
||||
@@ -301,8 +358,9 @@ class Artwork extends Model
|
||||
'slug' => $this->slug,
|
||||
'title' => $this->title,
|
||||
'description' => (string) ($this->description ?? ''),
|
||||
'author_id' => $this->user_id,
|
||||
'author_name' => $this->user?->name ?? 'Skinbase',
|
||||
'author_id' => $this->publishedAsId(),
|
||||
'author_name' => $this->group?->name ?? $this->user?->name ?? 'Skinbase',
|
||||
'published_as_type' => $this->publishedAsType(),
|
||||
'category' => $category,
|
||||
'content_type' => $content_type,
|
||||
'tags' => $tags,
|
||||
|
||||
37
app/Models/ArtworkContributor.php
Normal file
37
app/Models/ArtworkContributor.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;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
class ArtworkContributor extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = [
|
||||
'artwork_id',
|
||||
'user_id',
|
||||
'credit_role',
|
||||
'is_primary',
|
||||
'sort_order',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'is_primary' => 'boolean',
|
||||
'sort_order' => 'integer',
|
||||
];
|
||||
|
||||
public function artwork(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Artwork::class);
|
||||
}
|
||||
|
||||
public function user(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,7 @@ use App\Models\CollectionMember;
|
||||
use App\Models\CollectionSave;
|
||||
use App\Models\CollectionSurfacePlacement;
|
||||
use App\Models\CollectionSubmission;
|
||||
use App\Models\Group;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
@@ -120,6 +121,7 @@ class Collection extends Model
|
||||
|
||||
protected $fillable = [
|
||||
'user_id',
|
||||
'group_id',
|
||||
'managed_by_user_id',
|
||||
'title',
|
||||
'slug',
|
||||
@@ -263,6 +265,11 @@ class Collection extends Model
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
|
||||
public function group(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Group::class);
|
||||
}
|
||||
|
||||
public function managedBy(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class, 'managed_by_user_id');
|
||||
@@ -448,7 +455,17 @@ class Collection extends Model
|
||||
{
|
||||
$userId = $user instanceof User ? $user->id : $user;
|
||||
|
||||
return $userId !== null && (int) $userId === (int) $this->user_id;
|
||||
if ($userId === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((int) ($this->group_id ?? 0) > 0) {
|
||||
$group = $this->relationLoaded('group') ? $this->group : $this->group()->with('members')->first();
|
||||
|
||||
return $group?->hasActiveMember((int) $userId) ?? false;
|
||||
}
|
||||
|
||||
return (int) $userId === (int) $this->user_id;
|
||||
}
|
||||
|
||||
public function isPubliclyAccessible(): bool
|
||||
@@ -488,6 +505,12 @@ class Collection extends Model
|
||||
|
||||
public function displayOwnerName(): string
|
||||
{
|
||||
if ((int) ($this->group_id ?? 0) > 0) {
|
||||
$group = $this->relationLoaded('group') ? $this->group : $this->group()->first();
|
||||
|
||||
return (string) ($group?->name ?: 'Skinbase Group');
|
||||
}
|
||||
|
||||
if ($this->type === self::TYPE_EDITORIAL && $this->editorial_owner_mode === self::EDITORIAL_OWNER_SYSTEM) {
|
||||
return (string) ($this->editorial_owner_label ?: config('collections.editorial.system_owner_label', 'Skinbase Editorial'));
|
||||
}
|
||||
@@ -499,6 +522,10 @@ class Collection extends Model
|
||||
|
||||
public function displayOwnerUsername(): ?string
|
||||
{
|
||||
if ((int) ($this->group_id ?? 0) > 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($this->type === self::TYPE_EDITORIAL && $this->editorial_owner_mode === self::EDITORIAL_OWNER_SYSTEM) {
|
||||
return null;
|
||||
}
|
||||
@@ -526,6 +553,12 @@ class Collection extends Model
|
||||
return null;
|
||||
}
|
||||
|
||||
if ((int) ($this->group_id ?? 0) > 0) {
|
||||
$group = $this->relationLoaded('group') ? $this->group : $this->group()->with('members')->first();
|
||||
|
||||
return $group?->activeRoleFor((int) $userId);
|
||||
}
|
||||
|
||||
if ($this->isOwnedBy($userId)) {
|
||||
return self::MEMBER_ROLE_OWNER;
|
||||
}
|
||||
@@ -546,10 +579,13 @@ class Collection extends Model
|
||||
|
||||
public function canBeManagedBy(User $user): bool
|
||||
{
|
||||
return in_array($this->activeMemberRoleFor($user), [
|
||||
self::MEMBER_ROLE_OWNER,
|
||||
self::MEMBER_ROLE_EDITOR,
|
||||
], true);
|
||||
if ((int) ($this->group_id ?? 0) > 0) {
|
||||
$group = $this->relationLoaded('group') ? $this->group : $this->group()->with('members')->first();
|
||||
|
||||
return $group?->canManageCollections($user) ?? false;
|
||||
}
|
||||
|
||||
return in_array($this->activeMemberRoleFor($user), [self::MEMBER_ROLE_OWNER, self::MEMBER_ROLE_EDITOR], true);
|
||||
}
|
||||
|
||||
public function canManageArtworks(User $user): bool
|
||||
@@ -559,6 +595,12 @@ class Collection extends Model
|
||||
|
||||
public function canManageMembers(User $user): bool
|
||||
{
|
||||
if ((int) ($this->group_id ?? 0) > 0) {
|
||||
$group = $this->relationLoaded('group') ? $this->group : $this->group()->with('members')->first();
|
||||
|
||||
return $group?->canManageMembers($user) ?? false;
|
||||
}
|
||||
|
||||
return $this->isCollaborative() && $this->canBeManagedBy($user);
|
||||
}
|
||||
|
||||
|
||||
868
app/Models/Group.php
Normal file
868
app/Models/Group.php
Normal file
@@ -0,0 +1,868 @@
|
||||
<?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\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Database\Eloquent\Relations\HasOne;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use Laravel\Scout\Searchable;
|
||||
|
||||
class Group extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
use SoftDeletes;
|
||||
use Searchable;
|
||||
|
||||
public const VISIBILITY_PUBLIC = 'public';
|
||||
public const VISIBILITY_PRIVATE = 'private';
|
||||
public const VISIBILITY_UNLISTED = 'unlisted';
|
||||
|
||||
public const LIFECYCLE_ACTIVE = 'active';
|
||||
public const LIFECYCLE_ARCHIVED = 'archived';
|
||||
public const LIFECYCLE_SUSPENDED = 'suspended';
|
||||
|
||||
public const MEMBERSHIP_INVITE_ONLY = 'invite_only';
|
||||
public const MEMBERSHIP_REQUEST_TO_JOIN = 'request_to_join';
|
||||
public const MEMBERSHIP_OPEN = 'open';
|
||||
|
||||
public const ROLE_OWNER = 'owner';
|
||||
public const ROLE_ADMIN = 'admin';
|
||||
public const ROLE_EDITOR = 'editor';
|
||||
public const ROLE_MEMBER = 'member';
|
||||
public const ROLE_CONTRIBUTOR = 'contributor';
|
||||
|
||||
public const PERMISSION_REVIEW_JOIN_REQUESTS = 'review_join_requests';
|
||||
public const PERMISSION_REVIEW_SUBMISSIONS = 'review_submissions';
|
||||
public const PERMISSION_MANAGE_RECRUITMENT = 'manage_recruitment';
|
||||
public const PERMISSION_MANAGE_POSTS = 'manage_posts';
|
||||
public const PERMISSION_PUBLISH_POSTS = 'publish_posts';
|
||||
public const PERMISSION_PIN_POSTS = 'pin_posts';
|
||||
public const PERMISSION_MANAGE_MEMBER_PERMISSIONS = 'manage_member_permissions';
|
||||
public const PERMISSION_MANAGE_EVENTS = 'manage_events';
|
||||
public const PERMISSION_MANAGE_CHALLENGES = 'manage_challenges';
|
||||
public const PERMISSION_MANAGE_PROJECTS = 'manage_projects';
|
||||
public const PERMISSION_MANAGE_RELEASES = 'manage_releases';
|
||||
public const PERMISSION_PUBLISH_RELEASES = 'publish_releases';
|
||||
public const PERMISSION_MANAGE_MILESTONES = 'manage_milestones';
|
||||
public const PERMISSION_VIEW_REPUTATION_DASHBOARD = 'view_reputation_dashboard';
|
||||
public const PERMISSION_MANAGE_BADGES = 'manage_badges';
|
||||
public const PERMISSION_VIEW_INTERNAL_TRUST_METRICS = 'view_internal_trust_metrics';
|
||||
public const PERMISSION_FEATURE_RELEASES = 'feature_releases';
|
||||
public const PERMISSION_ASSIGN_RELEASE_LEAD = 'assign_release_lead';
|
||||
public const PERMISSION_MANAGE_ASSETS = 'manage_assets';
|
||||
public const PERMISSION_FEATURE_CHALLENGE_ENTRIES = 'feature_challenge_entries';
|
||||
public const PERMISSION_PUBLISH_EVENT_UPDATES = 'publish_event_updates';
|
||||
public const PERMISSION_ATTACH_ASSETS_TO_PROJECTS = 'attach_assets_to_projects';
|
||||
public const PERMISSION_VIEW_INTERNAL_ASSETS = 'view_internal_assets';
|
||||
public const PERMISSION_MANAGE_ACTIVITY_PINS = 'manage_activity_pins';
|
||||
|
||||
public const STATUS_PENDING = 'pending';
|
||||
public const STATUS_ACTIVE = 'active';
|
||||
public const STATUS_REVOKED = 'revoked';
|
||||
|
||||
protected $fillable = [
|
||||
'owner_user_id',
|
||||
'featured_artwork_id',
|
||||
'is_verified',
|
||||
'founded_at',
|
||||
'name',
|
||||
'slug',
|
||||
'headline',
|
||||
'bio',
|
||||
'type',
|
||||
'visibility',
|
||||
'status',
|
||||
'membership_policy',
|
||||
'website_url',
|
||||
'links_json',
|
||||
'avatar_path',
|
||||
'banner_path',
|
||||
'artworks_count',
|
||||
'collections_count',
|
||||
'followers_count',
|
||||
'last_activity_at',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'links_json' => 'array',
|
||||
'is_verified' => 'boolean',
|
||||
'artworks_count' => 'integer',
|
||||
'collections_count' => 'integer',
|
||||
'followers_count' => 'integer',
|
||||
'founded_at' => 'datetime',
|
||||
'last_activity_at' => 'datetime',
|
||||
];
|
||||
|
||||
public function owner(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class, 'owner_user_id');
|
||||
}
|
||||
|
||||
public function getRouteKeyName(): string
|
||||
{
|
||||
return 'slug';
|
||||
}
|
||||
|
||||
public function members(): HasMany
|
||||
{
|
||||
return $this->hasMany(GroupMember::class);
|
||||
}
|
||||
|
||||
public function invitations(): HasMany
|
||||
{
|
||||
return $this->hasMany(GroupInvitation::class);
|
||||
}
|
||||
|
||||
public function follows(): HasMany
|
||||
{
|
||||
return $this->hasMany(GroupFollow::class);
|
||||
}
|
||||
|
||||
public function artworks(): HasMany
|
||||
{
|
||||
return $this->hasMany(Artwork::class);
|
||||
}
|
||||
|
||||
public function collections(): HasMany
|
||||
{
|
||||
return $this->hasMany(Collection::class);
|
||||
}
|
||||
|
||||
public function joinRequests(): HasMany
|
||||
{
|
||||
return $this->hasMany(GroupJoinRequest::class);
|
||||
}
|
||||
|
||||
public function posts(): HasMany
|
||||
{
|
||||
return $this->hasMany(GroupPost::class);
|
||||
}
|
||||
|
||||
public function recruitmentProfile(): HasOne
|
||||
{
|
||||
return $this->hasOne(GroupRecruitmentProfile::class);
|
||||
}
|
||||
|
||||
public function projects(): HasMany
|
||||
{
|
||||
return $this->hasMany(GroupProject::class);
|
||||
}
|
||||
|
||||
public function releases(): HasMany
|
||||
{
|
||||
return $this->hasMany(GroupRelease::class);
|
||||
}
|
||||
|
||||
public function challenges(): HasMany
|
||||
{
|
||||
return $this->hasMany(GroupChallenge::class);
|
||||
}
|
||||
|
||||
public function events(): HasMany
|
||||
{
|
||||
return $this->hasMany(GroupEvent::class);
|
||||
}
|
||||
|
||||
public function assets(): HasMany
|
||||
{
|
||||
return $this->hasMany(GroupAsset::class);
|
||||
}
|
||||
|
||||
public function activityItems(): HasMany
|
||||
{
|
||||
return $this->hasMany(GroupActivityItem::class);
|
||||
}
|
||||
|
||||
public function contributorStats(): HasMany
|
||||
{
|
||||
return $this->hasMany(GroupContributorStat::class);
|
||||
}
|
||||
|
||||
public function badges(): HasMany
|
||||
{
|
||||
return $this->hasMany(GroupBadge::class);
|
||||
}
|
||||
|
||||
public function memberBadges(): HasMany
|
||||
{
|
||||
return $this->hasMany(GroupMemberBadge::class);
|
||||
}
|
||||
|
||||
public function discoveryMetric(): HasOne
|
||||
{
|
||||
return $this->hasOne(GroupDiscoveryMetric::class);
|
||||
}
|
||||
|
||||
public function historyEntries(): HasMany
|
||||
{
|
||||
return $this->hasMany(GroupHistory::class);
|
||||
}
|
||||
|
||||
public function scopePublic(Builder $query): Builder
|
||||
{
|
||||
return $query
|
||||
->where('visibility', self::VISIBILITY_PUBLIC)
|
||||
->where('status', self::LIFECYCLE_ACTIVE);
|
||||
}
|
||||
|
||||
public static function acceptedVisibilityValues(): array
|
||||
{
|
||||
return [self::VISIBILITY_PUBLIC, self::VISIBILITY_PRIVATE, self::VISIBILITY_UNLISTED];
|
||||
}
|
||||
|
||||
public static function acceptedMembershipPolicies(): array
|
||||
{
|
||||
return [self::MEMBERSHIP_INVITE_ONLY, self::MEMBERSHIP_REQUEST_TO_JOIN, self::MEMBERSHIP_OPEN];
|
||||
}
|
||||
|
||||
public static function normalizeMemberRole(string $role): string
|
||||
{
|
||||
$normalized = strtolower(trim($role));
|
||||
|
||||
return $normalized === self::ROLE_CONTRIBUTOR ? self::ROLE_MEMBER : $normalized;
|
||||
}
|
||||
|
||||
public static function displayRole(?string $role): ?string
|
||||
{
|
||||
if ($role === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return self::normalizeMemberRole($role) === self::ROLE_MEMBER ? self::ROLE_CONTRIBUTOR : self::normalizeMemberRole($role);
|
||||
}
|
||||
|
||||
public function isOwnedBy(User|int|null $user): bool
|
||||
{
|
||||
$userId = $user instanceof User ? $user->id : $user;
|
||||
|
||||
return $userId !== null && (int) $userId === (int) $this->owner_user_id;
|
||||
}
|
||||
|
||||
public function isPubliclyVisible(): bool
|
||||
{
|
||||
return in_array($this->visibility, [self::VISIBILITY_PUBLIC, self::VISIBILITY_UNLISTED], true)
|
||||
&& $this->status !== self::LIFECYCLE_SUSPENDED;
|
||||
}
|
||||
|
||||
public function isOperational(): bool
|
||||
{
|
||||
return $this->status === self::LIFECYCLE_ACTIVE;
|
||||
}
|
||||
|
||||
public function canArchive(User $user): bool
|
||||
{
|
||||
return $this->isOwnedBy($user);
|
||||
}
|
||||
|
||||
public function canViewStudio(User $user): bool
|
||||
{
|
||||
if ($this->status === self::LIFECYCLE_SUSPENDED) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->hasActiveMember($user);
|
||||
}
|
||||
|
||||
public function activeRoleFor(User|int|null $user): ?string
|
||||
{
|
||||
$userId = $user instanceof User ? $user->id : $user;
|
||||
|
||||
if ($userId === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($this->isOwnedBy($userId)) {
|
||||
return self::ROLE_OWNER;
|
||||
}
|
||||
|
||||
$members = $this->relationLoaded('members')
|
||||
? $this->members
|
||||
: $this->members()->where('status', self::STATUS_ACTIVE)->get();
|
||||
|
||||
return $members
|
||||
->first(fn (GroupMember $member): bool => (int) $member->user_id === (int) $userId && $member->status === self::STATUS_ACTIVE)
|
||||
?->role;
|
||||
}
|
||||
|
||||
public function hasActiveMember(User|int|null $user): bool
|
||||
{
|
||||
return $this->activeRoleFor($user) !== null;
|
||||
}
|
||||
|
||||
public function activeMembershipFor(User|int|null $user): ?GroupMember
|
||||
{
|
||||
$userId = $user instanceof User ? $user->id : $user;
|
||||
|
||||
if ($userId === null || $this->isOwnedBy($userId)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$members = $this->relationLoaded('members')
|
||||
? $this->members
|
||||
: $this->members()->where('status', self::STATUS_ACTIVE)->get();
|
||||
|
||||
return $members->first(
|
||||
fn (GroupMember $member): bool => (int) $member->user_id === (int) $userId && $member->status === self::STATUS_ACTIVE
|
||||
);
|
||||
}
|
||||
|
||||
public function permissionOverridesFor(User|int|null $user): array
|
||||
{
|
||||
if ($this->isOwnedBy($user)) {
|
||||
return collect(self::allowedPermissionOverrides())
|
||||
->mapWithKeys(fn (string $permission): array => [$permission => true])
|
||||
->all();
|
||||
}
|
||||
|
||||
$member = $this->activeMembershipFor($user);
|
||||
if (! $member) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return collect($member->permission_overrides_json ?? [])
|
||||
->mapWithKeys(function ($override): array {
|
||||
if (is_array($override)) {
|
||||
$key = trim((string) ($override['key'] ?? ''));
|
||||
if ($key === '' || ! in_array($key, self::allowedPermissionOverrides(), true)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return [$key => (bool) ($override['is_allowed'] ?? false)];
|
||||
}
|
||||
|
||||
$key = trim((string) $override);
|
||||
if ($key === '' || ! in_array($key, self::allowedPermissionOverrides(), true)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return [$key => true];
|
||||
})
|
||||
->all();
|
||||
}
|
||||
|
||||
public function hasPermission(User|int|null $user, string $permission): bool
|
||||
{
|
||||
return $this->permissionOverridesFor($user)[$permission] ?? false;
|
||||
}
|
||||
|
||||
public function hasDeniedPermission(User|int|null $user, string $permission): bool
|
||||
{
|
||||
$overrides = $this->permissionOverridesFor($user);
|
||||
|
||||
return array_key_exists($permission, $overrides) && $overrides[$permission] === false;
|
||||
}
|
||||
|
||||
public static function permissionKeys(): array
|
||||
{
|
||||
return self::allowedPermissionOverrides();
|
||||
}
|
||||
|
||||
public function canBeViewedBy(?User $user): bool
|
||||
{
|
||||
if ($this->isPubliclyVisible()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return $user !== null && $this->hasActiveMember($user);
|
||||
}
|
||||
|
||||
public function canManage(User $user): bool
|
||||
{
|
||||
return in_array($this->activeRoleFor($user), [self::ROLE_OWNER, self::ROLE_ADMIN], true);
|
||||
}
|
||||
|
||||
public function canManageMembers(User $user): bool
|
||||
{
|
||||
return in_array($this->activeRoleFor($user), [self::ROLE_OWNER, self::ROLE_ADMIN], true);
|
||||
}
|
||||
|
||||
public function canPublishArtworks(User $user): bool
|
||||
{
|
||||
return $this->isOperational()
|
||||
&& in_array($this->activeRoleFor($user), [self::ROLE_OWNER, self::ROLE_ADMIN, self::ROLE_EDITOR], true);
|
||||
}
|
||||
|
||||
public function canCreateArtworkDrafts(User $user): bool
|
||||
{
|
||||
return $this->isOperational() && $this->hasActiveMember($user);
|
||||
}
|
||||
|
||||
public function canSubmitArtworkForReview(User $user): bool
|
||||
{
|
||||
return $this->isOperational() && $this->hasActiveMember($user);
|
||||
}
|
||||
|
||||
public function canReviewSubmissions(User $user): bool
|
||||
{
|
||||
if (! $this->isOperational()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($this->hasDeniedPermission($user, self::PERMISSION_REVIEW_SUBMISSIONS)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$role = $this->activeRoleFor($user);
|
||||
|
||||
return in_array($role, [self::ROLE_OWNER, self::ROLE_ADMIN, self::ROLE_EDITOR], true)
|
||||
|| $this->hasPermission($user, self::PERMISSION_REVIEW_SUBMISSIONS);
|
||||
}
|
||||
|
||||
public function canRequestJoin(?User $user): bool
|
||||
{
|
||||
if (! $this->isOperational() || $user === null || $this->hasActiveMember($user)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return in_array($this->membership_policy, [self::MEMBERSHIP_REQUEST_TO_JOIN, self::MEMBERSHIP_OPEN], true);
|
||||
}
|
||||
|
||||
public function canReviewJoinRequests(User $user): bool
|
||||
{
|
||||
if (! $this->isOperational()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($this->hasDeniedPermission($user, self::PERMISSION_REVIEW_JOIN_REQUESTS)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$role = $this->activeRoleFor($user);
|
||||
|
||||
return in_array($role, [self::ROLE_OWNER, self::ROLE_ADMIN], true)
|
||||
|| $this->hasPermission($user, self::PERMISSION_REVIEW_JOIN_REQUESTS);
|
||||
}
|
||||
|
||||
public function canManageRecruitment(User $user): bool
|
||||
{
|
||||
if (! $this->isOperational()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($this->hasDeniedPermission($user, self::PERMISSION_MANAGE_RECRUITMENT)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$role = $this->activeRoleFor($user);
|
||||
|
||||
return in_array($role, [self::ROLE_OWNER, self::ROLE_ADMIN, self::ROLE_EDITOR], true)
|
||||
|| $this->hasPermission($user, self::PERMISSION_MANAGE_RECRUITMENT);
|
||||
}
|
||||
|
||||
public function canManagePosts(User $user): bool
|
||||
{
|
||||
if (! $this->isOperational()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($this->hasDeniedPermission($user, self::PERMISSION_MANAGE_POSTS)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$role = $this->activeRoleFor($user);
|
||||
|
||||
return in_array($role, [self::ROLE_OWNER, self::ROLE_ADMIN, self::ROLE_EDITOR], true)
|
||||
|| $this->hasPermission($user, self::PERMISSION_MANAGE_POSTS);
|
||||
}
|
||||
|
||||
public function canPublishPosts(User $user): bool
|
||||
{
|
||||
if (! $this->isOperational()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($this->hasDeniedPermission($user, self::PERMISSION_PUBLISH_POSTS)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$role = $this->activeRoleFor($user);
|
||||
|
||||
return in_array($role, [self::ROLE_OWNER, self::ROLE_ADMIN, self::ROLE_EDITOR], true)
|
||||
|| $this->hasPermission($user, self::PERMISSION_PUBLISH_POSTS)
|
||||
|| $this->canManagePosts($user);
|
||||
}
|
||||
|
||||
public function canPinPosts(User $user): bool
|
||||
{
|
||||
if (! $this->isOperational()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($this->hasDeniedPermission($user, self::PERMISSION_PIN_POSTS)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return in_array($this->activeRoleFor($user), [self::ROLE_OWNER, self::ROLE_ADMIN], true)
|
||||
|| $this->hasPermission($user, self::PERMISSION_PIN_POSTS);
|
||||
}
|
||||
|
||||
public function canManageMemberPermissions(User $user): bool
|
||||
{
|
||||
if (! $this->isOperational()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($this->hasDeniedPermission($user, self::PERMISSION_MANAGE_MEMBER_PERMISSIONS)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return in_array($this->activeRoleFor($user), [self::ROLE_OWNER, self::ROLE_ADMIN], true)
|
||||
|| $this->hasPermission($user, self::PERMISSION_MANAGE_MEMBER_PERMISSIONS);
|
||||
}
|
||||
|
||||
public function canManageEvents(User $user): bool
|
||||
{
|
||||
if (! $this->isOperational()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($this->hasDeniedPermission($user, self::PERMISSION_MANAGE_EVENTS)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return in_array($this->activeRoleFor($user), [self::ROLE_OWNER, self::ROLE_ADMIN, self::ROLE_EDITOR], true)
|
||||
|| $this->hasPermission($user, self::PERMISSION_MANAGE_EVENTS);
|
||||
}
|
||||
|
||||
public function canManageChallenges(User $user): bool
|
||||
{
|
||||
if (! $this->isOperational()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($this->hasDeniedPermission($user, self::PERMISSION_MANAGE_CHALLENGES)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return in_array($this->activeRoleFor($user), [self::ROLE_OWNER, self::ROLE_ADMIN, self::ROLE_EDITOR], true)
|
||||
|| $this->hasPermission($user, self::PERMISSION_MANAGE_CHALLENGES);
|
||||
}
|
||||
|
||||
public function canManageProjects(User $user): bool
|
||||
{
|
||||
if (! $this->isOperational()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($this->hasDeniedPermission($user, self::PERMISSION_MANAGE_PROJECTS)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return in_array($this->activeRoleFor($user), [self::ROLE_OWNER, self::ROLE_ADMIN, self::ROLE_EDITOR], true)
|
||||
|| $this->hasPermission($user, self::PERMISSION_MANAGE_PROJECTS);
|
||||
}
|
||||
|
||||
public function canManageReleases(User $user): bool
|
||||
{
|
||||
if (! $this->isOperational()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($this->hasDeniedPermission($user, self::PERMISSION_MANAGE_RELEASES)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->canManageProjects($user)
|
||||
|| $this->hasPermission($user, self::PERMISSION_MANAGE_RELEASES);
|
||||
}
|
||||
|
||||
public function canPublishReleases(User $user): bool
|
||||
{
|
||||
if (! $this->isOperational()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($this->hasDeniedPermission($user, self::PERMISSION_PUBLISH_RELEASES)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return in_array($this->activeRoleFor($user), [self::ROLE_OWNER, self::ROLE_ADMIN], true)
|
||||
|| $this->hasPermission($user, self::PERMISSION_PUBLISH_RELEASES)
|
||||
|| $this->canManageReleases($user);
|
||||
}
|
||||
|
||||
public function canManageMilestones(User $user): bool
|
||||
{
|
||||
if (! $this->isOperational()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($this->hasDeniedPermission($user, self::PERMISSION_MANAGE_MILESTONES)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->canManageProjects($user)
|
||||
|| $this->canManageReleases($user)
|
||||
|| $this->hasPermission($user, self::PERMISSION_MANAGE_MILESTONES);
|
||||
}
|
||||
|
||||
public function canViewReputationDashboard(User $user): bool
|
||||
{
|
||||
if (! $this->isOperational()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($this->hasDeniedPermission($user, self::PERMISSION_VIEW_REPUTATION_DASHBOARD)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return in_array($this->activeRoleFor($user), [self::ROLE_OWNER, self::ROLE_ADMIN], true)
|
||||
|| $this->hasPermission($user, self::PERMISSION_VIEW_REPUTATION_DASHBOARD);
|
||||
}
|
||||
|
||||
public function canManageBadges(User $user): bool
|
||||
{
|
||||
if (! $this->isOperational()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($this->hasDeniedPermission($user, self::PERMISSION_MANAGE_BADGES)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return in_array($this->activeRoleFor($user), [self::ROLE_OWNER, self::ROLE_ADMIN], true)
|
||||
|| $this->hasPermission($user, self::PERMISSION_MANAGE_BADGES);
|
||||
}
|
||||
|
||||
public function canViewInternalTrustMetrics(User $user): bool
|
||||
{
|
||||
if (! $this->isOperational()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($this->hasDeniedPermission($user, self::PERMISSION_VIEW_INTERNAL_TRUST_METRICS)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return in_array($this->activeRoleFor($user), [self::ROLE_OWNER, self::ROLE_ADMIN], true)
|
||||
|| $this->hasPermission($user, self::PERMISSION_VIEW_INTERNAL_TRUST_METRICS);
|
||||
}
|
||||
|
||||
public function canFeatureReleases(User $user): bool
|
||||
{
|
||||
if (! $this->isOperational()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($this->hasDeniedPermission($user, self::PERMISSION_FEATURE_RELEASES)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return in_array($this->activeRoleFor($user), [self::ROLE_OWNER, self::ROLE_ADMIN], true)
|
||||
|| $this->hasPermission($user, self::PERMISSION_FEATURE_RELEASES);
|
||||
}
|
||||
|
||||
public function canAssignReleaseLead(User $user): bool
|
||||
{
|
||||
if (! $this->isOperational()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($this->hasDeniedPermission($user, self::PERMISSION_ASSIGN_RELEASE_LEAD)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->canManageReleases($user)
|
||||
|| $this->hasPermission($user, self::PERMISSION_ASSIGN_RELEASE_LEAD);
|
||||
}
|
||||
|
||||
public function canManageAssets(User $user): bool
|
||||
{
|
||||
if (! $this->isOperational()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($this->hasDeniedPermission($user, self::PERMISSION_MANAGE_ASSETS)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return in_array($this->activeRoleFor($user), [self::ROLE_OWNER, self::ROLE_ADMIN, self::ROLE_EDITOR], true)
|
||||
|| $this->hasPermission($user, self::PERMISSION_MANAGE_ASSETS);
|
||||
}
|
||||
|
||||
public function canFeatureChallengeEntries(User $user): bool
|
||||
{
|
||||
if (! $this->isOperational()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($this->hasDeniedPermission($user, self::PERMISSION_FEATURE_CHALLENGE_ENTRIES)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return in_array($this->activeRoleFor($user), [self::ROLE_OWNER, self::ROLE_ADMIN, self::ROLE_EDITOR], true)
|
||||
|| $this->hasPermission($user, self::PERMISSION_FEATURE_CHALLENGE_ENTRIES);
|
||||
}
|
||||
|
||||
public function canPublishEventUpdates(User $user): bool
|
||||
{
|
||||
if (! $this->isOperational()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($this->hasDeniedPermission($user, self::PERMISSION_PUBLISH_EVENT_UPDATES)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->canManageEvents($user)
|
||||
|| $this->hasPermission($user, self::PERMISSION_PUBLISH_EVENT_UPDATES);
|
||||
}
|
||||
|
||||
public function canAttachAssetsToProjects(User $user): bool
|
||||
{
|
||||
if (! $this->isOperational()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($this->hasDeniedPermission($user, self::PERMISSION_ATTACH_ASSETS_TO_PROJECTS)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->canManageProjects($user)
|
||||
|| $this->hasPermission($user, self::PERMISSION_ATTACH_ASSETS_TO_PROJECTS);
|
||||
}
|
||||
|
||||
public function canViewInternalAssets(User $user): bool
|
||||
{
|
||||
if (! $this->isOperational()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($this->hasDeniedPermission($user, self::PERMISSION_VIEW_INTERNAL_ASSETS)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return in_array($this->activeRoleFor($user), [self::ROLE_OWNER, self::ROLE_ADMIN, self::ROLE_EDITOR], true)
|
||||
|| $this->hasPermission($user, self::PERMISSION_VIEW_INTERNAL_ASSETS);
|
||||
}
|
||||
|
||||
public function canPinActivity(User $user): bool
|
||||
{
|
||||
if (! $this->isOperational()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($this->hasDeniedPermission($user, self::PERMISSION_MANAGE_ACTIVITY_PINS)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return in_array($this->activeRoleFor($user), [self::ROLE_OWNER, self::ROLE_ADMIN], true)
|
||||
|| $this->hasPermission($user, self::PERMISSION_MANAGE_ACTIVITY_PINS);
|
||||
}
|
||||
|
||||
public static function allowedPermissionOverrides(): array
|
||||
{
|
||||
return [
|
||||
self::PERMISSION_REVIEW_JOIN_REQUESTS,
|
||||
self::PERMISSION_REVIEW_SUBMISSIONS,
|
||||
self::PERMISSION_MANAGE_RECRUITMENT,
|
||||
self::PERMISSION_MANAGE_POSTS,
|
||||
self::PERMISSION_PUBLISH_POSTS,
|
||||
self::PERMISSION_PIN_POSTS,
|
||||
self::PERMISSION_MANAGE_MEMBER_PERMISSIONS,
|
||||
self::PERMISSION_MANAGE_EVENTS,
|
||||
self::PERMISSION_MANAGE_CHALLENGES,
|
||||
self::PERMISSION_MANAGE_PROJECTS,
|
||||
self::PERMISSION_MANAGE_RELEASES,
|
||||
self::PERMISSION_PUBLISH_RELEASES,
|
||||
self::PERMISSION_MANAGE_MILESTONES,
|
||||
self::PERMISSION_VIEW_REPUTATION_DASHBOARD,
|
||||
self::PERMISSION_MANAGE_BADGES,
|
||||
self::PERMISSION_VIEW_INTERNAL_TRUST_METRICS,
|
||||
self::PERMISSION_FEATURE_RELEASES,
|
||||
self::PERMISSION_ASSIGN_RELEASE_LEAD,
|
||||
self::PERMISSION_MANAGE_ASSETS,
|
||||
self::PERMISSION_FEATURE_CHALLENGE_ENTRIES,
|
||||
self::PERMISSION_PUBLISH_EVENT_UPDATES,
|
||||
self::PERMISSION_ATTACH_ASSETS_TO_PROJECTS,
|
||||
self::PERMISSION_VIEW_INTERNAL_ASSETS,
|
||||
self::PERMISSION_MANAGE_ACTIVITY_PINS,
|
||||
];
|
||||
}
|
||||
|
||||
public function canManageCollections(User $user): bool
|
||||
{
|
||||
return $this->isOperational() && $this->canPublishArtworks($user);
|
||||
}
|
||||
|
||||
public function avatarUrl(): ?string
|
||||
{
|
||||
return $this->assetUrl($this->avatar_path);
|
||||
}
|
||||
|
||||
public function bannerUrl(): ?string
|
||||
{
|
||||
return $this->assetUrl($this->banner_path);
|
||||
}
|
||||
|
||||
public function publicUrl(): string
|
||||
{
|
||||
return route('groups.show', ['group' => $this->slug]);
|
||||
}
|
||||
|
||||
public function shouldBeSearchable(): bool
|
||||
{
|
||||
return $this->visibility === self::VISIBILITY_PUBLIC && $this->status === self::LIFECYCLE_ACTIVE;
|
||||
}
|
||||
|
||||
public function toSearchableArray(): array
|
||||
{
|
||||
$recruitment = $this->relationLoaded('recruitmentProfile')
|
||||
? $this->recruitmentProfile
|
||||
: $this->recruitmentProfile()->first();
|
||||
|
||||
$memberNames = $this->members()
|
||||
->with('user:id,name,username')
|
||||
->where('status', self::STATUS_ACTIVE)
|
||||
->limit(12)
|
||||
->get()
|
||||
->map(fn (GroupMember $member): string => (string) ($member->user?->name ?: $member->user?->username ?: ''))
|
||||
->filter()
|
||||
->values()
|
||||
->all();
|
||||
|
||||
return [
|
||||
'id' => (int) $this->id,
|
||||
'name' => (string) $this->name,
|
||||
'slug' => (string) $this->slug,
|
||||
'headline' => (string) ($this->headline ?? ''),
|
||||
'bio' => (string) ($this->bio ?? ''),
|
||||
'type' => (string) ($this->type ?? ''),
|
||||
'visibility' => (string) $this->visibility,
|
||||
'status' => (string) ($this->status ?? self::LIFECYCLE_ACTIVE),
|
||||
'artworks_count' => (int) ($this->artworks_count ?? 0),
|
||||
'followers_count' => (int) ($this->followers_count ?? 0),
|
||||
'is_recruiting' => (bool) ($recruitment?->is_recruiting ?? false),
|
||||
'recruitment_headline' => (string) ($recruitment?->headline ?? ''),
|
||||
'recruitment_roles' => array_values(array_filter($recruitment?->roles_json ?? [])),
|
||||
'recruitment_skills' => array_values(array_filter($recruitment?->skills_json ?? [])),
|
||||
'release_titles' => $this->releases()->where('visibility', GroupRelease::VISIBILITY_PUBLIC)->latest('published_at')->limit(6)->pluck('title')->filter()->values()->all(),
|
||||
'project_titles' => $this->projects()->where('visibility', GroupProject::VISIBILITY_PUBLIC)->latest('updated_at')->limit(6)->pluck('title')->filter()->values()->all(),
|
||||
'challenge_titles' => $this->challenges()->where('visibility', GroupChallenge::VISIBILITY_PUBLIC)->latest('updated_at')->limit(6)->pluck('title')->filter()->values()->all(),
|
||||
'event_titles' => $this->events()->where('visibility', GroupEvent::VISIBILITY_PUBLIC)->latest('start_at')->limit(6)->pluck('title')->filter()->values()->all(),
|
||||
'badge_keys' => $this->badges()->latest('awarded_at')->limit(6)->pluck('badge_key')->filter()->values()->all(),
|
||||
'member_names' => $memberNames,
|
||||
];
|
||||
}
|
||||
|
||||
private function assetUrl(?string $path): ?string
|
||||
{
|
||||
$trimmed = trim((string) $path);
|
||||
|
||||
if ($trimmed === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (str_starts_with($trimmed, 'http://') || str_starts_with($trimmed, 'https://')) {
|
||||
return $trimmed;
|
||||
}
|
||||
|
||||
return rtrim((string) config('cdn.files_url', 'https://files.skinbase.org'), '/') . '/' . ltrim($trimmed, '/');
|
||||
}
|
||||
}
|
||||
45
app/Models/GroupActivityItem.php
Normal file
45
app/Models/GroupActivityItem.php
Normal file
@@ -0,0 +1,45 @@
|
||||
<?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 GroupActivityItem extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
public const VISIBILITY_PUBLIC = 'public';
|
||||
public const VISIBILITY_INTERNAL = 'internal';
|
||||
|
||||
protected $fillable = [
|
||||
'group_id',
|
||||
'type',
|
||||
'visibility',
|
||||
'actor_user_id',
|
||||
'subject_type',
|
||||
'subject_id',
|
||||
'headline',
|
||||
'summary',
|
||||
'is_pinned',
|
||||
'occurred_at',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'is_pinned' => 'boolean',
|
||||
'occurred_at' => 'datetime',
|
||||
];
|
||||
|
||||
public function group(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Group::class);
|
||||
}
|
||||
|
||||
public function actor(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class, 'actor_user_id');
|
||||
}
|
||||
}
|
||||
87
app/Models/GroupAsset.php
Normal file
87
app/Models/GroupAsset.php
Normal file
@@ -0,0 +1,87 @@
|
||||
<?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\SoftDeletes;
|
||||
|
||||
class GroupAsset extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
use SoftDeletes;
|
||||
|
||||
public const CATEGORY_LOGO = 'logo';
|
||||
public const CATEGORY_BRAND = 'brand';
|
||||
public const CATEGORY_PALETTE = 'palette';
|
||||
public const CATEGORY_WATERMARK = 'watermark';
|
||||
public const CATEGORY_TEMPLATE = 'template';
|
||||
public const CATEGORY_REFERENCE = 'reference';
|
||||
public const CATEGORY_SOURCE_PACK = 'source_pack';
|
||||
public const CATEGORY_PROMO = 'promo';
|
||||
public const CATEGORY_MISC = 'misc';
|
||||
|
||||
public const VISIBILITY_INTERNAL = 'internal';
|
||||
public const VISIBILITY_MEMBERS_ONLY = 'members_only';
|
||||
public const VISIBILITY_PUBLIC_DOWNLOAD = 'public_download';
|
||||
|
||||
public const STATUS_ACTIVE = 'active';
|
||||
public const STATUS_ARCHIVED = 'archived';
|
||||
|
||||
protected $fillable = [
|
||||
'group_id',
|
||||
'title',
|
||||
'description',
|
||||
'category',
|
||||
'file_path',
|
||||
'preview_path',
|
||||
'visibility',
|
||||
'status',
|
||||
'linked_project_id',
|
||||
'uploaded_by_user_id',
|
||||
'approved_by_user_id',
|
||||
'is_featured',
|
||||
'file_meta_json',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'is_featured' => 'boolean',
|
||||
'file_meta_json' => 'array',
|
||||
];
|
||||
|
||||
public function group(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Group::class);
|
||||
}
|
||||
|
||||
public function linkedProject(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(GroupProject::class, 'linked_project_id');
|
||||
}
|
||||
|
||||
public function uploader(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class, 'uploaded_by_user_id');
|
||||
}
|
||||
|
||||
public function approver(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class, 'approved_by_user_id');
|
||||
}
|
||||
|
||||
public function canBeViewedBy(?User $viewer): bool
|
||||
{
|
||||
if ($this->status !== self::STATUS_ACTIVE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return match ($this->visibility) {
|
||||
self::VISIBILITY_PUBLIC_DOWNLOAD => $this->group->canBeViewedBy($viewer),
|
||||
self::VISIBILITY_MEMBERS_ONLY => $viewer !== null && $this->group->hasActiveMember($viewer),
|
||||
default => $viewer !== null && $this->group->canViewInternalAssets($viewer),
|
||||
};
|
||||
}
|
||||
}
|
||||
31
app/Models/GroupBadge.php
Normal file
31
app/Models/GroupBadge.php
Normal file
@@ -0,0 +1,31 @@
|
||||
<?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 GroupBadge extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = [
|
||||
'group_id',
|
||||
'badge_key',
|
||||
'awarded_at',
|
||||
'meta_json',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'awarded_at' => 'datetime',
|
||||
'meta_json' => 'array',
|
||||
];
|
||||
|
||||
public function group(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Group::class);
|
||||
}
|
||||
}
|
||||
125
app/Models/GroupChallenge.php
Normal file
125
app/Models/GroupChallenge.php
Normal file
@@ -0,0 +1,125 @@
|
||||
<?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\BelongsToMany;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
class GroupChallenge extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
use SoftDeletes;
|
||||
|
||||
public const VISIBILITY_PUBLIC = 'public';
|
||||
public const VISIBILITY_UNLISTED = 'unlisted';
|
||||
public const VISIBILITY_PRIVATE = 'private';
|
||||
|
||||
public const PARTICIPATION_GROUP_ONLY = 'group_only';
|
||||
public const PARTICIPATION_INVITE_ONLY = 'invite_only';
|
||||
public const PARTICIPATION_PUBLIC = 'public';
|
||||
|
||||
public const STATUS_DRAFT = 'draft';
|
||||
public const STATUS_PUBLISHED = 'published';
|
||||
public const STATUS_ACTIVE = 'active';
|
||||
public const STATUS_ENDED = 'ended';
|
||||
public const STATUS_ARCHIVED = 'archived';
|
||||
|
||||
protected $fillable = [
|
||||
'group_id',
|
||||
'title',
|
||||
'slug',
|
||||
'summary',
|
||||
'description',
|
||||
'cover_path',
|
||||
'visibility',
|
||||
'participation_scope',
|
||||
'status',
|
||||
'start_at',
|
||||
'end_at',
|
||||
'rules_text',
|
||||
'submission_instructions',
|
||||
'judging_mode',
|
||||
'linked_collection_id',
|
||||
'linked_project_id',
|
||||
'created_by_user_id',
|
||||
'featured_artwork_id',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'start_at' => 'datetime',
|
||||
'end_at' => 'datetime',
|
||||
];
|
||||
|
||||
public function getRouteKeyName(): string
|
||||
{
|
||||
return 'slug';
|
||||
}
|
||||
|
||||
public function group(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Group::class);
|
||||
}
|
||||
|
||||
public function creator(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class, 'created_by_user_id');
|
||||
}
|
||||
|
||||
public function linkedCollection(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Collection::class, 'linked_collection_id');
|
||||
}
|
||||
|
||||
public function linkedProject(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(GroupProject::class, 'linked_project_id');
|
||||
}
|
||||
|
||||
public function featuredArtwork(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Artwork::class, 'featured_artwork_id');
|
||||
}
|
||||
|
||||
public function artworkLinks(): HasMany
|
||||
{
|
||||
return $this->hasMany(GroupChallengeArtwork::class);
|
||||
}
|
||||
|
||||
public function artworks(): BelongsToMany
|
||||
{
|
||||
return $this->belongsToMany(Artwork::class, 'group_challenge_artworks')
|
||||
->withPivot(['submitted_by_user_id', 'sort_order'])
|
||||
->withTimestamps()
|
||||
->orderBy('group_challenge_artworks.sort_order');
|
||||
}
|
||||
|
||||
public function canBeViewedBy(?User $viewer): bool
|
||||
{
|
||||
if ($this->visibility !== self::VISIBILITY_PRIVATE) {
|
||||
return $this->group->canBeViewedBy($viewer);
|
||||
}
|
||||
|
||||
return $viewer !== null && $this->group->canViewStudio($viewer);
|
||||
}
|
||||
|
||||
public function coverUrl(): ?string
|
||||
{
|
||||
$path = trim((string) $this->cover_path);
|
||||
|
||||
if ($path === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (str_starts_with($path, 'http://') || str_starts_with($path, 'https://')) {
|
||||
return $path;
|
||||
}
|
||||
|
||||
return rtrim((string) config('cdn.files_url', 'https://files.skinbase.org'), '/') . '/' . ltrim($path, '/');
|
||||
}
|
||||
}
|
||||
36
app/Models/GroupChallengeArtwork.php
Normal file
36
app/Models/GroupChallengeArtwork.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 GroupChallengeArtwork extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = [
|
||||
'group_challenge_id',
|
||||
'artwork_id',
|
||||
'submitted_by_user_id',
|
||||
'sort_order',
|
||||
];
|
||||
|
||||
public function challenge(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(GroupChallenge::class, 'group_challenge_id');
|
||||
}
|
||||
|
||||
public function artwork(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Artwork::class);
|
||||
}
|
||||
|
||||
public function submitter(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class, 'submitted_by_user_id');
|
||||
}
|
||||
}
|
||||
44
app/Models/GroupContributorStat.php
Normal file
44
app/Models/GroupContributorStat.php
Normal file
@@ -0,0 +1,44 @@
|
||||
<?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 GroupContributorStat extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = [
|
||||
'group_id',
|
||||
'user_id',
|
||||
'credited_artworks_count',
|
||||
'release_count',
|
||||
'project_count',
|
||||
'review_actions_count',
|
||||
'approved_submissions_count',
|
||||
'reputation_meta_json',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'credited_artworks_count' => 'integer',
|
||||
'release_count' => 'integer',
|
||||
'project_count' => 'integer',
|
||||
'review_actions_count' => 'integer',
|
||||
'approved_submissions_count' => 'integer',
|
||||
'reputation_meta_json' => 'array',
|
||||
];
|
||||
|
||||
public function group(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Group::class);
|
||||
}
|
||||
|
||||
public function user(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
}
|
||||
38
app/Models/GroupDiscoveryMetric.php
Normal file
38
app/Models/GroupDiscoveryMetric.php
Normal file
@@ -0,0 +1,38 @@
|
||||
<?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 GroupDiscoveryMetric extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = [
|
||||
'group_id',
|
||||
'freshness_score',
|
||||
'activity_score',
|
||||
'release_score',
|
||||
'trust_score',
|
||||
'collaboration_score',
|
||||
'last_calculated_at',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'freshness_score' => 'float',
|
||||
'activity_score' => 'float',
|
||||
'release_score' => 'float',
|
||||
'trust_score' => 'float',
|
||||
'collaboration_score' => 'float',
|
||||
'last_calculated_at' => 'datetime',
|
||||
];
|
||||
|
||||
public function group(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Group::class);
|
||||
}
|
||||
}
|
||||
118
app/Models/GroupEvent.php
Normal file
118
app/Models/GroupEvent.php
Normal file
@@ -0,0 +1,118 @@
|
||||
<?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\SoftDeletes;
|
||||
|
||||
class GroupEvent extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
use SoftDeletes;
|
||||
|
||||
public const TYPE_LAUNCH = 'launch';
|
||||
public const TYPE_CHALLENGE = 'challenge';
|
||||
public const TYPE_LIVESTREAM = 'livestream';
|
||||
public const TYPE_MEETUP = 'meetup';
|
||||
public const TYPE_MILESTONE = 'milestone';
|
||||
public const TYPE_SHOWCASE = 'showcase';
|
||||
public const TYPE_INTERNAL_SESSION = 'internal_session';
|
||||
public const TYPE_RELEASE_WINDOW = 'release_window';
|
||||
|
||||
public const VISIBILITY_PUBLIC = 'public';
|
||||
public const VISIBILITY_MEMBERS_ONLY = 'members_only';
|
||||
public const VISIBILITY_PRIVATE = 'private';
|
||||
|
||||
public const STATUS_DRAFT = 'draft';
|
||||
public const STATUS_PUBLISHED = 'published';
|
||||
public const STATUS_ARCHIVED = 'archived';
|
||||
public const STATUS_CANCELLED = 'cancelled';
|
||||
|
||||
protected $fillable = [
|
||||
'group_id',
|
||||
'title',
|
||||
'slug',
|
||||
'summary',
|
||||
'description',
|
||||
'event_type',
|
||||
'visibility',
|
||||
'start_at',
|
||||
'end_at',
|
||||
'timezone',
|
||||
'cover_path',
|
||||
'location',
|
||||
'external_url',
|
||||
'linked_project_id',
|
||||
'linked_collection_id',
|
||||
'linked_challenge_id',
|
||||
'status',
|
||||
'is_featured',
|
||||
'created_by_user_id',
|
||||
'published_at',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'start_at' => 'datetime',
|
||||
'end_at' => 'datetime',
|
||||
'is_featured' => 'boolean',
|
||||
'published_at' => 'datetime',
|
||||
];
|
||||
|
||||
public function getRouteKeyName(): string
|
||||
{
|
||||
return 'slug';
|
||||
}
|
||||
|
||||
public function group(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Group::class);
|
||||
}
|
||||
|
||||
public function creator(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class, 'created_by_user_id');
|
||||
}
|
||||
|
||||
public function linkedProject(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(GroupProject::class, 'linked_project_id');
|
||||
}
|
||||
|
||||
public function linkedCollection(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Collection::class, 'linked_collection_id');
|
||||
}
|
||||
|
||||
public function linkedChallenge(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(GroupChallenge::class, 'linked_challenge_id');
|
||||
}
|
||||
|
||||
public function canBeViewedBy(?User $viewer): bool
|
||||
{
|
||||
return match ($this->visibility) {
|
||||
self::VISIBILITY_PUBLIC => $this->group->canBeViewedBy($viewer),
|
||||
self::VISIBILITY_MEMBERS_ONLY => $viewer !== null && $this->group->hasActiveMember($viewer),
|
||||
default => $viewer !== null && $this->group->canViewStudio($viewer),
|
||||
};
|
||||
}
|
||||
|
||||
public function coverUrl(): ?string
|
||||
{
|
||||
$path = trim((string) $this->cover_path);
|
||||
|
||||
if ($path === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (str_starts_with($path, 'http://') || str_starts_with($path, 'https://')) {
|
||||
return $path;
|
||||
}
|
||||
|
||||
return rtrim((string) config('cdn.files_url', 'https://files.skinbase.org'), '/') . '/' . ltrim($path, '/');
|
||||
}
|
||||
}
|
||||
29
app/Models/GroupFollow.php
Normal file
29
app/Models/GroupFollow.php
Normal file
@@ -0,0 +1,29 @@
|
||||
<?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 GroupFollow extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = [
|
||||
'group_id',
|
||||
'user_id',
|
||||
];
|
||||
|
||||
public function group(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Group::class);
|
||||
}
|
||||
|
||||
public function user(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
}
|
||||
44
app/Models/GroupHistory.php
Normal file
44
app/Models/GroupHistory.php
Normal file
@@ -0,0 +1,44 @@
|
||||
<?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 GroupHistory extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
public $timestamps = false;
|
||||
|
||||
protected $fillable = [
|
||||
'group_id',
|
||||
'actor_user_id',
|
||||
'action_type',
|
||||
'target_type',
|
||||
'target_id',
|
||||
'summary',
|
||||
'before_json',
|
||||
'after_json',
|
||||
'created_at',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'before_json' => 'array',
|
||||
'after_json' => 'array',
|
||||
'created_at' => 'datetime',
|
||||
];
|
||||
|
||||
public function group(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Group::class);
|
||||
}
|
||||
|
||||
public function actor(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class, 'actor_user_id');
|
||||
}
|
||||
}
|
||||
69
app/Models/GroupInvitation.php
Normal file
69
app/Models/GroupInvitation.php
Normal file
@@ -0,0 +1,69 @@
|
||||
<?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 GroupInvitation extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
public const STATUS_PENDING = 'pending';
|
||||
public const STATUS_ACCEPTED = 'accepted';
|
||||
public const STATUS_DECLINED = 'declined';
|
||||
public const STATUS_REVOKED = 'revoked';
|
||||
public const STATUS_EXPIRED = 'expired';
|
||||
|
||||
protected $fillable = [
|
||||
'group_id',
|
||||
'invited_user_id',
|
||||
'invited_by_user_id',
|
||||
'source_group_member_id',
|
||||
'role',
|
||||
'status',
|
||||
'token',
|
||||
'note',
|
||||
'invited_at',
|
||||
'expires_at',
|
||||
'responded_at',
|
||||
'accepted_at',
|
||||
'revoked_at',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'invited_at' => 'datetime',
|
||||
'expires_at' => 'datetime',
|
||||
'responded_at' => 'datetime',
|
||||
'accepted_at' => 'datetime',
|
||||
'revoked_at' => 'datetime',
|
||||
];
|
||||
|
||||
public function getRouteKeyName(): string
|
||||
{
|
||||
return 'token';
|
||||
}
|
||||
|
||||
public function group(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Group::class);
|
||||
}
|
||||
|
||||
public function invitedUser(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class, 'invited_user_id');
|
||||
}
|
||||
|
||||
public function invitedBy(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class, 'invited_by_user_id');
|
||||
}
|
||||
|
||||
public function sourceGroupMember(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(GroupMember::class, 'source_group_member_id');
|
||||
}
|
||||
}
|
||||
55
app/Models/GroupJoinRequest.php
Normal file
55
app/Models/GroupJoinRequest.php
Normal file
@@ -0,0 +1,55 @@
|
||||
<?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 GroupJoinRequest extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
public const STATUS_PENDING = 'pending';
|
||||
public const STATUS_APPROVED = 'approved';
|
||||
public const STATUS_REJECTED = 'rejected';
|
||||
public const STATUS_WITHDRAWN = 'withdrawn';
|
||||
public const STATUS_EXPIRED = 'expired';
|
||||
|
||||
protected $fillable = [
|
||||
'group_id',
|
||||
'user_id',
|
||||
'message',
|
||||
'portfolio_url',
|
||||
'desired_role',
|
||||
'skills_json',
|
||||
'status',
|
||||
'reviewed_by_user_id',
|
||||
'review_notes',
|
||||
'reviewed_at',
|
||||
'expires_at',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'skills_json' => 'array',
|
||||
'reviewed_at' => 'datetime',
|
||||
'expires_at' => 'datetime',
|
||||
];
|
||||
|
||||
public function group(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Group::class);
|
||||
}
|
||||
|
||||
public function user(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
|
||||
public function reviewedBy(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class, 'reviewed_by_user_id');
|
||||
}
|
||||
}
|
||||
51
app/Models/GroupMember.php
Normal file
51
app/Models/GroupMember.php
Normal file
@@ -0,0 +1,51 @@
|
||||
<?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 GroupMember extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = [
|
||||
'group_id',
|
||||
'user_id',
|
||||
'invited_by_user_id',
|
||||
'role',
|
||||
'status',
|
||||
'permission_overrides_json',
|
||||
'note',
|
||||
'invited_at',
|
||||
'expires_at',
|
||||
'accepted_at',
|
||||
'revoked_at',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'permission_overrides_json' => 'array',
|
||||
'invited_at' => 'datetime',
|
||||
'expires_at' => 'datetime',
|
||||
'accepted_at' => 'datetime',
|
||||
'revoked_at' => 'datetime',
|
||||
];
|
||||
|
||||
public function group(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Group::class);
|
||||
}
|
||||
|
||||
public function user(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
|
||||
public function invitedBy(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class, 'invited_by_user_id');
|
||||
}
|
||||
}
|
||||
37
app/Models/GroupMemberBadge.php
Normal file
37
app/Models/GroupMemberBadge.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;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
class GroupMemberBadge extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = [
|
||||
'group_id',
|
||||
'user_id',
|
||||
'badge_key',
|
||||
'awarded_at',
|
||||
'meta_json',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'awarded_at' => 'datetime',
|
||||
'meta_json' => 'array',
|
||||
];
|
||||
|
||||
public function group(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Group::class);
|
||||
}
|
||||
|
||||
public function user(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
}
|
||||
59
app/Models/GroupPost.php
Normal file
59
app/Models/GroupPost.php
Normal file
@@ -0,0 +1,59 @@
|
||||
<?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\SoftDeletes;
|
||||
|
||||
class GroupPost extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
use SoftDeletes;
|
||||
|
||||
public const TYPE_ANNOUNCEMENT = 'announcement';
|
||||
public const TYPE_RELEASE = 'release';
|
||||
public const TYPE_RECRUITMENT = 'recruitment';
|
||||
public const TYPE_UPDATE = 'update';
|
||||
|
||||
public const STATUS_DRAFT = 'draft';
|
||||
public const STATUS_PUBLISHED = 'published';
|
||||
public const STATUS_ARCHIVED = 'archived';
|
||||
|
||||
protected $fillable = [
|
||||
'group_id',
|
||||
'author_user_id',
|
||||
'type',
|
||||
'title',
|
||||
'slug',
|
||||
'excerpt',
|
||||
'content',
|
||||
'cover_path',
|
||||
'status',
|
||||
'is_pinned',
|
||||
'published_at',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'is_pinned' => 'boolean',
|
||||
'published_at' => 'datetime',
|
||||
];
|
||||
|
||||
public function getRouteKeyName(): string
|
||||
{
|
||||
return 'slug';
|
||||
}
|
||||
|
||||
public function group(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Group::class);
|
||||
}
|
||||
|
||||
public function author(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class, 'author_user_id');
|
||||
}
|
||||
}
|
||||
162
app/Models/GroupProject.php
Normal file
162
app/Models/GroupProject.php
Normal file
@@ -0,0 +1,162 @@
|
||||
<?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\BelongsToMany;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
class GroupProject extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
use SoftDeletes;
|
||||
|
||||
public const STATUS_PLANNED = 'planned';
|
||||
public const STATUS_ACTIVE = 'active';
|
||||
public const STATUS_REVIEW = 'review';
|
||||
public const STATUS_RELEASED = 'released';
|
||||
public const STATUS_ARCHIVED = 'archived';
|
||||
|
||||
public const VISIBILITY_PUBLIC = 'public';
|
||||
public const VISIBILITY_UNLISTED = 'unlisted';
|
||||
public const VISIBILITY_PRIVATE = 'private';
|
||||
|
||||
protected $fillable = [
|
||||
'group_id',
|
||||
'title',
|
||||
'slug',
|
||||
'summary',
|
||||
'description',
|
||||
'cover_path',
|
||||
'status',
|
||||
'visibility',
|
||||
'start_date',
|
||||
'target_date',
|
||||
'released_at',
|
||||
'created_by_user_id',
|
||||
'lead_user_id',
|
||||
'linked_collection_id',
|
||||
'linked_featured_artwork_id',
|
||||
'pinned_post_id',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'start_date' => 'date',
|
||||
'target_date' => 'date',
|
||||
'released_at' => 'datetime',
|
||||
];
|
||||
|
||||
public function getRouteKeyName(): string
|
||||
{
|
||||
return 'slug';
|
||||
}
|
||||
|
||||
public function group(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Group::class);
|
||||
}
|
||||
|
||||
public function creator(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class, 'created_by_user_id');
|
||||
}
|
||||
|
||||
public function lead(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class, 'lead_user_id');
|
||||
}
|
||||
|
||||
public function linkedCollection(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Collection::class, 'linked_collection_id');
|
||||
}
|
||||
|
||||
public function featuredArtwork(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Artwork::class, 'linked_featured_artwork_id');
|
||||
}
|
||||
|
||||
public function pinnedPost(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(GroupPost::class, 'pinned_post_id');
|
||||
}
|
||||
|
||||
public function artworkLinks(): HasMany
|
||||
{
|
||||
return $this->hasMany(GroupProjectArtwork::class);
|
||||
}
|
||||
|
||||
public function artworks(): BelongsToMany
|
||||
{
|
||||
return $this->belongsToMany(Artwork::class, 'group_project_artworks')
|
||||
->withPivot(['sort_order'])
|
||||
->withTimestamps()
|
||||
->orderBy('group_project_artworks.sort_order');
|
||||
}
|
||||
|
||||
public function memberLinks(): HasMany
|
||||
{
|
||||
return $this->hasMany(GroupProjectMember::class);
|
||||
}
|
||||
|
||||
public function members(): BelongsToMany
|
||||
{
|
||||
return $this->belongsToMany(User::class, 'group_project_members')
|
||||
->withPivot(['role_label', 'is_lead'])
|
||||
->withTimestamps();
|
||||
}
|
||||
|
||||
public function assets(): HasMany
|
||||
{
|
||||
return $this->hasMany(GroupAsset::class, 'linked_project_id');
|
||||
}
|
||||
|
||||
public function milestones(): HasMany
|
||||
{
|
||||
return $this->hasMany(GroupProjectMilestone::class)->orderBy('sort_order');
|
||||
}
|
||||
|
||||
public function releases(): HasMany
|
||||
{
|
||||
return $this->hasMany(GroupRelease::class, 'linked_project_id');
|
||||
}
|
||||
|
||||
public function challenges(): HasMany
|
||||
{
|
||||
return $this->hasMany(GroupChallenge::class, 'linked_project_id');
|
||||
}
|
||||
|
||||
public function events(): HasMany
|
||||
{
|
||||
return $this->hasMany(GroupEvent::class, 'linked_project_id');
|
||||
}
|
||||
|
||||
public function canBeViewedBy(?User $viewer): bool
|
||||
{
|
||||
if ($this->visibility !== self::VISIBILITY_PRIVATE) {
|
||||
return $this->group->canBeViewedBy($viewer);
|
||||
}
|
||||
|
||||
return $viewer !== null && $this->group->canViewStudio($viewer);
|
||||
}
|
||||
|
||||
public function coverUrl(): ?string
|
||||
{
|
||||
$path = trim((string) $this->cover_path);
|
||||
|
||||
if ($path === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (str_starts_with($path, 'http://') || str_starts_with($path, 'https://')) {
|
||||
return $path;
|
||||
}
|
||||
|
||||
return rtrim((string) config('cdn.files_url', 'https://files.skinbase.org'), '/') . '/' . ltrim($path, '/');
|
||||
}
|
||||
}
|
||||
30
app/Models/GroupProjectArtwork.php
Normal file
30
app/Models/GroupProjectArtwork.php
Normal file
@@ -0,0 +1,30 @@
|
||||
<?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 GroupProjectArtwork extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = [
|
||||
'group_project_id',
|
||||
'artwork_id',
|
||||
'sort_order',
|
||||
];
|
||||
|
||||
public function project(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(GroupProject::class, 'group_project_id');
|
||||
}
|
||||
|
||||
public function artwork(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Artwork::class);
|
||||
}
|
||||
}
|
||||
35
app/Models/GroupProjectMember.php
Normal file
35
app/Models/GroupProjectMember.php
Normal file
@@ -0,0 +1,35 @@
|
||||
<?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 GroupProjectMember extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = [
|
||||
'group_project_id',
|
||||
'user_id',
|
||||
'role_label',
|
||||
'is_lead',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'is_lead' => 'boolean',
|
||||
];
|
||||
|
||||
public function project(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(GroupProject::class, 'group_project_id');
|
||||
}
|
||||
|
||||
public function user(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
}
|
||||
45
app/Models/GroupProjectMilestone.php
Normal file
45
app/Models/GroupProjectMilestone.php
Normal file
@@ -0,0 +1,45 @@
|
||||
<?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 GroupProjectMilestone extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
public const STATUS_PENDING = 'pending';
|
||||
public const STATUS_ACTIVE = 'active';
|
||||
public const STATUS_BLOCKED = 'blocked';
|
||||
public const STATUS_COMPLETED = 'completed';
|
||||
public const STATUS_CANCELLED = 'cancelled';
|
||||
|
||||
protected $fillable = [
|
||||
'group_project_id',
|
||||
'title',
|
||||
'summary',
|
||||
'status',
|
||||
'due_date',
|
||||
'owner_user_id',
|
||||
'sort_order',
|
||||
'notes',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'due_date' => 'date',
|
||||
];
|
||||
|
||||
public function project(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(GroupProject::class, 'group_project_id');
|
||||
}
|
||||
|
||||
public function owner(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class, 'owner_user_id');
|
||||
}
|
||||
}
|
||||
36
app/Models/GroupRecruitmentProfile.php
Normal file
36
app/Models/GroupRecruitmentProfile.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 GroupRecruitmentProfile extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = [
|
||||
'group_id',
|
||||
'is_recruiting',
|
||||
'headline',
|
||||
'description',
|
||||
'roles_json',
|
||||
'skills_json',
|
||||
'contact_mode',
|
||||
'visibility',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'is_recruiting' => 'boolean',
|
||||
'roles_json' => 'array',
|
||||
'skills_json' => 'array',
|
||||
];
|
||||
|
||||
public function group(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Group::class);
|
||||
}
|
||||
}
|
||||
156
app/Models/GroupRelease.php
Normal file
156
app/Models/GroupRelease.php
Normal file
@@ -0,0 +1,156 @@
|
||||
<?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\BelongsToMany;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
class GroupRelease extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
use SoftDeletes;
|
||||
|
||||
public const STATUS_PLANNED = 'planned';
|
||||
public const STATUS_IN_PROGRESS = 'in_progress';
|
||||
public const STATUS_INTERNAL_REVIEW = 'internal_review';
|
||||
public const STATUS_SCHEDULED = 'scheduled';
|
||||
public const STATUS_RELEASED = 'released';
|
||||
public const STATUS_ARCHIVED = 'archived';
|
||||
public const STATUS_CANCELLED = 'cancelled';
|
||||
|
||||
public const STAGE_CONCEPT = 'concept';
|
||||
public const STAGE_PRODUCTION = 'production';
|
||||
public const STAGE_REVIEW = 'review';
|
||||
public const STAGE_PACKAGING = 'packaging';
|
||||
public const STAGE_APPROVAL = 'approval';
|
||||
public const STAGE_PUBLISHING = 'publishing';
|
||||
public const STAGE_RELEASED = 'released';
|
||||
|
||||
public const VISIBILITY_PUBLIC = 'public';
|
||||
public const VISIBILITY_UNLISTED = 'unlisted';
|
||||
public const VISIBILITY_PRIVATE = 'private';
|
||||
|
||||
protected $fillable = [
|
||||
'group_id',
|
||||
'title',
|
||||
'slug',
|
||||
'summary',
|
||||
'description',
|
||||
'cover_path',
|
||||
'status',
|
||||
'current_stage',
|
||||
'visibility',
|
||||
'planned_release_at',
|
||||
'released_at',
|
||||
'lead_user_id',
|
||||
'linked_project_id',
|
||||
'linked_collection_id',
|
||||
'featured_artwork_id',
|
||||
'release_notes',
|
||||
'created_by_user_id',
|
||||
'published_at',
|
||||
'is_featured',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'planned_release_at' => 'datetime',
|
||||
'released_at' => 'datetime',
|
||||
'published_at' => 'datetime',
|
||||
'is_featured' => 'boolean',
|
||||
];
|
||||
|
||||
public function getRouteKeyName(): string
|
||||
{
|
||||
return 'slug';
|
||||
}
|
||||
|
||||
public function group(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Group::class);
|
||||
}
|
||||
|
||||
public function creator(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class, 'created_by_user_id');
|
||||
}
|
||||
|
||||
public function lead(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class, 'lead_user_id');
|
||||
}
|
||||
|
||||
public function linkedProject(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(GroupProject::class, 'linked_project_id');
|
||||
}
|
||||
|
||||
public function linkedCollection(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Collection::class, 'linked_collection_id');
|
||||
}
|
||||
|
||||
public function featuredArtwork(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Artwork::class, 'featured_artwork_id');
|
||||
}
|
||||
|
||||
public function artworkLinks(): HasMany
|
||||
{
|
||||
return $this->hasMany(GroupReleaseArtwork::class)->orderBy('sort_order');
|
||||
}
|
||||
|
||||
public function artworks(): BelongsToMany
|
||||
{
|
||||
return $this->belongsToMany(Artwork::class, 'group_release_artworks')
|
||||
->withPivot(['sort_order'])
|
||||
->withTimestamps()
|
||||
->orderBy('group_release_artworks.sort_order');
|
||||
}
|
||||
|
||||
public function contributorLinks(): HasMany
|
||||
{
|
||||
return $this->hasMany(GroupReleaseContributor::class)->orderBy('sort_order');
|
||||
}
|
||||
|
||||
public function contributors(): BelongsToMany
|
||||
{
|
||||
return $this->belongsToMany(User::class, 'group_release_contributors')
|
||||
->withPivot(['role_label', 'sort_order'])
|
||||
->withTimestamps();
|
||||
}
|
||||
|
||||
public function milestones(): HasMany
|
||||
{
|
||||
return $this->hasMany(GroupReleaseMilestone::class)->orderBy('sort_order');
|
||||
}
|
||||
|
||||
public function canBeViewedBy(?User $viewer): bool
|
||||
{
|
||||
if ($this->visibility !== self::VISIBILITY_PRIVATE) {
|
||||
return $this->group->canBeViewedBy($viewer);
|
||||
}
|
||||
|
||||
return $viewer !== null && $this->group->canViewStudio($viewer);
|
||||
}
|
||||
|
||||
public function coverUrl(): ?string
|
||||
{
|
||||
$path = trim((string) $this->cover_path);
|
||||
|
||||
if ($path === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (str_starts_with($path, 'http://') || str_starts_with($path, 'https://')) {
|
||||
return $path;
|
||||
}
|
||||
|
||||
return rtrim((string) config('cdn.files_url', 'https://files.skinbase.org'), '/') . '/' . ltrim($path, '/');
|
||||
}
|
||||
}
|
||||
30
app/Models/GroupReleaseArtwork.php
Normal file
30
app/Models/GroupReleaseArtwork.php
Normal file
@@ -0,0 +1,30 @@
|
||||
<?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 GroupReleaseArtwork extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = [
|
||||
'group_release_id',
|
||||
'artwork_id',
|
||||
'sort_order',
|
||||
];
|
||||
|
||||
public function release(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(GroupRelease::class, 'group_release_id');
|
||||
}
|
||||
|
||||
public function artwork(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Artwork::class);
|
||||
}
|
||||
}
|
||||
31
app/Models/GroupReleaseContributor.php
Normal file
31
app/Models/GroupReleaseContributor.php
Normal file
@@ -0,0 +1,31 @@
|
||||
<?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 GroupReleaseContributor extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = [
|
||||
'group_release_id',
|
||||
'user_id',
|
||||
'role_label',
|
||||
'sort_order',
|
||||
];
|
||||
|
||||
public function release(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(GroupRelease::class, 'group_release_id');
|
||||
}
|
||||
|
||||
public function user(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
}
|
||||
45
app/Models/GroupReleaseMilestone.php
Normal file
45
app/Models/GroupReleaseMilestone.php
Normal file
@@ -0,0 +1,45 @@
|
||||
<?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 GroupReleaseMilestone extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
public const STATUS_PENDING = 'pending';
|
||||
public const STATUS_ACTIVE = 'active';
|
||||
public const STATUS_BLOCKED = 'blocked';
|
||||
public const STATUS_COMPLETED = 'completed';
|
||||
public const STATUS_CANCELLED = 'cancelled';
|
||||
|
||||
protected $fillable = [
|
||||
'group_release_id',
|
||||
'title',
|
||||
'summary',
|
||||
'status',
|
||||
'due_date',
|
||||
'owner_user_id',
|
||||
'sort_order',
|
||||
'notes',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'due_date' => 'date',
|
||||
];
|
||||
|
||||
public function release(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(GroupRelease::class, 'group_release_id');
|
||||
}
|
||||
|
||||
public function owner(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class, 'owner_user_id');
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,7 @@ class Leaderboard extends Model
|
||||
|
||||
public const TYPE_CREATOR = 'creator';
|
||||
public const TYPE_ARTWORK = 'artwork';
|
||||
public const TYPE_GROUP = 'group';
|
||||
public const TYPE_STORY = 'story';
|
||||
|
||||
public const PERIOD_DAILY = 'daily';
|
||||
|
||||
@@ -3,6 +3,10 @@
|
||||
namespace App\Models;
|
||||
|
||||
// use Illuminate\Contracts\Auth\MustVerifyEmail;
|
||||
use App\Models\Group;
|
||||
use App\Models\GroupFollow;
|
||||
use App\Models\GroupInvitation;
|
||||
use App\Models\GroupMember;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
@@ -125,6 +129,26 @@ class User extends Authenticatable
|
||||
return $this->hasMany(Collection::class)->latest('updated_at');
|
||||
}
|
||||
|
||||
public function ownedGroups(): HasMany
|
||||
{
|
||||
return $this->hasMany(Group::class, 'owner_user_id')->latest('updated_at');
|
||||
}
|
||||
|
||||
public function groupMemberships(): HasMany
|
||||
{
|
||||
return $this->hasMany(GroupMember::class)->latest('updated_at');
|
||||
}
|
||||
|
||||
public function groupInvitations(): HasMany
|
||||
{
|
||||
return $this->hasMany(GroupInvitation::class, 'invited_user_id')->latest('updated_at');
|
||||
}
|
||||
|
||||
public function groupFollows(): HasMany
|
||||
{
|
||||
return $this->hasMany(GroupFollow::class)->latest('updated_at');
|
||||
}
|
||||
|
||||
public function savedCollectionLists(): HasMany
|
||||
{
|
||||
return $this->hasMany(CollectionSavedList::class, 'user_id')->orderBy('title');
|
||||
|
||||
Reference in New Issue
Block a user