refactor: improve avatar error handling, storage URL resolution, and upload logging
All checks were successful
Setup EC2 Tools / setup-server (push) Successful in 1m46s

This commit is contained in:
vickytechkey 2026-09-10 23:05:37 +05:30
parent c899d7db13
commit 30c6fc5747
4 changed files with 78 additions and 7 deletions

View file

@ -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) {

View file

@ -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

View file

@ -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);
},
); );
} }

View file

@ -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 />