refactor: improve avatar error handling, storage URL resolution, and upload logging
All checks were successful
Setup EC2 Tools / setup-server (push) Successful in 1m46s
All checks were successful
Setup EC2 Tools / setup-server (push) Successful in 1m46s
This commit is contained in:
parent
c899d7db13
commit
30c6fc5747
4 changed files with 78 additions and 7 deletions
|
|
@ -48,7 +48,16 @@ function uploadFromUrl(?string $url, string $directory = 'social-accounts'): ?st
|
||||||
$extension
|
$extension
|
||||||
);
|
);
|
||||||
|
|
||||||
Storage::put($filename, $response->body(), 'public');
|
$stored = Storage::put($filename, $response->body(), 'public');
|
||||||
|
|
||||||
|
if (! $stored) {
|
||||||
|
Log::warning('uploadFromUrl: Storage::put failed', [
|
||||||
|
'url' => $url,
|
||||||
|
'filename' => $filename,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return $filename;
|
return $filename;
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@
|
||||||
use App\Enums\SocialAccount\Platform as SocialPlatform;
|
use App\Enums\SocialAccount\Platform as SocialPlatform;
|
||||||
use Database\Factories\PostPlatformFactory;
|
use Database\Factories\PostPlatformFactory;
|
||||||
use Illuminate\Database\Eloquent\Builder;
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
|
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||||
use Illuminate\Database\Eloquent\Concerns\HasUuids;
|
use Illuminate\Database\Eloquent\Concerns\HasUuids;
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
@ -20,6 +21,10 @@ class PostPlatform extends Model
|
||||||
/** @use HasFactory<PostPlatformFactory> */
|
/** @use HasFactory<PostPlatformFactory> */
|
||||||
use HasFactory, HasUuids;
|
use HasFactory, HasUuids;
|
||||||
|
|
||||||
|
protected $appends = [
|
||||||
|
'display_avatar',
|
||||||
|
];
|
||||||
|
|
||||||
protected $fillable = [
|
protected $fillable = [
|
||||||
'post_id',
|
'post_id',
|
||||||
'social_account_id',
|
'social_account_id',
|
||||||
|
|
@ -117,7 +122,40 @@ public function getDisplayAvatarAttribute(): ?string
|
||||||
return $this->socialAccount->avatar_url;
|
return $this->socialAccount->avatar_url;
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->platform_avatar ? Storage::url($this->platform_avatar) : null;
|
if (! $this->platform_avatar) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (str_starts_with($this->platform_avatar, 'http://') || str_starts_with($this->platform_avatar, 'https://')) {
|
||||||
|
return $this->platform_avatar;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Storage::disk('public')->exists($this->platform_avatar)) {
|
||||||
|
return Storage::disk('public')->url($this->platform_avatar);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Storage::url($this->platform_avatar);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function platformAvatar(): Attribute
|
||||||
|
{
|
||||||
|
return Attribute::make(
|
||||||
|
get: function (?string $value): ?string {
|
||||||
|
if (! $value) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (str_starts_with($value, 'http://') || str_starts_with($value, 'https://')) {
|
||||||
|
return $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Storage::disk('public')->exists($value)) {
|
||||||
|
return Storage::disk('public')->url($value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Storage::url($value);
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function markAsPublishing(): void
|
public function markAsPublishing(): void
|
||||||
|
|
|
||||||
|
|
@ -253,7 +253,21 @@ public function needsProactiveTokenRefresh(): bool
|
||||||
protected function avatarUrl(): Attribute
|
protected function avatarUrl(): Attribute
|
||||||
{
|
{
|
||||||
return Attribute::make(
|
return Attribute::make(
|
||||||
get: fn (?string $value) => $value ? Storage::url($value) : null,
|
get: function (?string $value): ?string {
|
||||||
|
if (! $value) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (str_starts_with($value, 'http://') || str_starts_with($value, 'https://')) {
|
||||||
|
return $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Storage::disk('public')->exists($value)) {
|
||||||
|
return Storage::disk('public')->url($value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Storage::url($value);
|
||||||
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { HTMLAttributes } from "vue"
|
import type { HTMLAttributes } from "vue"
|
||||||
import { computed } from "vue"
|
import { computed, ref, watch } from "vue"
|
||||||
import { AvatarRoot } from "reka-ui"
|
import { AvatarRoot } from "reka-ui"
|
||||||
|
import { IconUser } from "@tabler/icons-vue"
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils"
|
||||||
import { getInitials } from "@/composables/useInitials"
|
import { getInitials } from "@/composables/useInitials"
|
||||||
|
|
||||||
|
|
@ -12,7 +13,14 @@ const props = defineProps<{
|
||||||
fallbackClass?: HTMLAttributes["class"]
|
fallbackClass?: HTMLAttributes["class"]
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
|
const hasError = ref(false)
|
||||||
|
|
||||||
|
watch(() => props.src, () => {
|
||||||
|
hasError.value = false
|
||||||
|
})
|
||||||
|
|
||||||
const hasProps = computed(() => props.src !== undefined || props.name !== undefined)
|
const hasProps = computed(() => props.src !== undefined || props.name !== undefined)
|
||||||
|
const initials = computed(() => (props.name ? getInitials(props.name) : ''))
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
@ -22,16 +30,18 @@ const hasProps = computed(() => props.src !== undefined || props.name !== undefi
|
||||||
>
|
>
|
||||||
<template v-if="hasProps">
|
<template v-if="hasProps">
|
||||||
<img
|
<img
|
||||||
v-if="src"
|
v-if="src && !hasError"
|
||||||
:src="src"
|
:src="src"
|
||||||
:alt="name"
|
:alt="name"
|
||||||
class="aspect-square size-full object-cover"
|
class="aspect-square size-full object-cover"
|
||||||
|
@error="hasError = true"
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
v-else
|
v-else
|
||||||
:class="cn('flex size-full items-center justify-center bg-muted font-medium', props.fallbackClass)"
|
:class="cn('flex size-full items-center justify-center bg-muted text-xs font-bold text-foreground select-none', props.fallbackClass)"
|
||||||
>
|
>
|
||||||
{{ name ? getInitials(name) : '' }}
|
<span v-if="initials">{{ initials }}</span>
|
||||||
|
<IconUser v-else class="size-1/2 opacity-70" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<slot v-else />
|
<slot v-else />
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue