*/ use HasFactory, HasTranslations, SoftDeletes; protected $guarded = ['id']; /** @var array */ public array $translatable = ['title', 'description']; protected $casts = [ 'theme' => 'array', 'is_published' => 'boolean', 'ulid' => 'string', ]; protected static function newFactory(): BioPageFactory { return BioPageFactory::new(); } protected static function booted(): void { static::creating(function (self $model) { if (empty($model->ulid)) { $model->ulid = (string) Str::ulid(); } }); } /** @return HasMany */ public function blocks(): HasMany { return $this->hasMany(BioBlock::class)->orderBy('position'); } /** @return BelongsTo */ public function workspace(): BelongsTo { return $this->belongsTo(Workspace::class); } /** @return BelongsTo */ public function domain(): BelongsTo { return $this->belongsTo(Domain::class); } /** Canonical public URL — path-based for Free, subdomain/custom for Pro+ */ public function publicUrl(): string { $apex = config('services.hetzner.zone', 'nimu.li'); $ws = $this->workspace; if (! $ws) { return ''; } // Custom domain (Pro+ only, not apex subdomain). Require dot prefix so // "evilnimu.li" is not treated as an apex subdomain of nimu.li. if ($this->subdomain_host && ! str_ends_with($this->subdomain_host, '.'.$apex)) { return "https://{$this->subdomain_host}/b/{$this->slug}"; } // Pro+ workspace subdomain if (! $ws->isFreePlan() && $ws->subdomain_full) { return "https://{$ws->subdomain_full}/b/{$this->slug}"; } // Free plan (or fallback): apex path-based return "https://{$apex}/b/{$ws->slug}/{$this->slug}"; } public function shortPublicUrl(): string { return (string) preg_replace('|^https?://|', '', $this->publicUrl()); } }