<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Yoram Kornatzky</title>
    <description>The latest articles on DEV Community by Yoram Kornatzky (@kornatzky).</description>
    <link>https://dev.to/kornatzky</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F485231%2F3d1690d1-f67c-4d15-bc47-0740e9f5bd59.jpeg</url>
      <title>DEV Community: Yoram Kornatzky</title>
      <link>https://dev.to/kornatzky</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/kornatzky"/>
    <language>en</language>
    <item>
      <title>Phone Validation in Laravel Using Abstract</title>
      <dc:creator>Yoram Kornatzky</dc:creator>
      <pubDate>Mon, 17 Jun 2024 17:18:03 +0000</pubDate>
      <link>https://dev.to/kornatzky/phone-validation-in-laravel-using-abstract-f6l</link>
      <guid>https://dev.to/kornatzky/phone-validation-in-laravel-using-abstract-f6l</guid>
      <description>&lt;p&gt;Our sales events platform &lt;a href="https://auctibles.com" rel="noopener noreferrer"&gt;Auctibles&lt;/a&gt;, collects phone numbers for several purposes:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Contact phone of sellers to be displayed to buyers&lt;/li&gt;
&lt;li&gt;Landline phone of sellers&lt;/li&gt;
&lt;li&gt;Mobile phone of sellers for coordination of deliveries&lt;/li&gt;
&lt;li&gt;Mobile phone of buyers for coordination of deliveries&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Phone validation in Laravel is a crucial step for our sales events platform. It plays a significant role in ensuring smooth delivery coordination and reducing the risk of fraud. &lt;/p&gt;

&lt;p&gt;We use validation rules, a practical feature of the PHP Laravel framework, to ensure reliable validation. This is where the Abstract comes in, enhancing the functionality of our platform. &lt;/p&gt;

&lt;h1&gt;
  
  
  The Validation Rule
&lt;/h1&gt;

&lt;p&gt;We define a validation rule, &lt;code&gt;AbstractPhoneValidation&lt;/code&gt; that receives two parameters:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The type of phone number expected - mobile, landline, or an empty string when any type is expected, &lt;/li&gt;
&lt;li&gt;The country of the phone number - two letters ISO 3166-1 alpha-2 code&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The rule uses our Abstract API key from the configuration file.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    namespace App\Rules;

    use Closure;
    use Illuminate\Contracts\Validation\ValidationRule;
    use Illuminate\Support\Str;

    class AbstractPhoneValidation implements ValidationRule
    {
        /**
         * Create a new rule instance.
         *
         * @return void
         */
        public function __construct(
            private string $phone_type,
            private string $country,
        ) {}    


        /**
         * Run the validation rule.
         *
         * @param  \Closure(string): \Illuminate\Translation\PotentiallyTranslatedString  $fail
         */
        public function validate(string $attribute, mixed $value, Closure $fail): void
        {
            // Initialize cURL.
            $ch = curl_init();

            // Set the URL that you want to GET by using the CURLOPT_URL option.
            curl_setopt(
              $ch,
              CURLOPT_URL,
              'https://phonevalidation.abstractapi.com/v1/?api_key=' . config('app.ABSTRACT_PHONE_VERIFICATION_KEY') . "&amp;amp;phone=$value"
            );

            // Set CURLOPT_RETURNTRANSFER so that the content is returned as a variable.
            curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

            // Set CURLOPT_FOLLOWLOCATION to true to follow redirects.
            curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);

            // Execute the request.
            $data = curl_exec($ch);

            // Close the cURL handle.
            curl_close($ch);

                    //The response from Abstract is a JSON
            $data = json_decode($data);

                    // here, we do the validation
            if (!$data-&amp;gt;valid || 
                // Is the phone number valid?
                ($this-&amp;gt;phone_type &amp;amp;&amp;amp; Str::lower($data-&amp;gt;type) != $this-&amp;gt;phone_type) || 
                // If the phone type was given, what the phone number of the required type?
                ($data-&amp;gt;country-&amp;gt;code != $this-&amp;gt;country)
                // Does the country of the phone correspond to the expected country?
            ) 
                    {
                $fail('validation.' . $attribute)-&amp;gt;translate();
            }

        }
    }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;As Abstract returns the phone type capitalized, we use &lt;code&gt;Str::lower&lt;/code&gt; to convert it to lowercase to correspond to our labeling of phone types.&lt;/p&gt;

&lt;h1&gt;
  
  
  Using the Validation Rule
&lt;/h1&gt;

&lt;p&gt;We use the rule in validation, where the user's country two letters ISO 3166-1 alpha-2 code is stored in &lt;code&gt;$country&lt;/code&gt;:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$rules_array = [
    'mobile_phone' =&amp;gt; [new AbstractPhoneValidation('mobile', $country)],
    'landline_phone' =&amp;gt; [new AbstractPhoneValidation('landline', $country)],
    'contact_phone' =&amp;gt; [new AbstractPhoneValidation('', $this-&amp;gt;country)],
]);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;For a &lt;code&gt;contact_phone&lt;/code&gt;, any type of phone number is acceptable.&lt;/p&gt;

</description>
      <category>php</category>
      <category>laravel</category>
      <category>abstract</category>
      <category>phone</category>
    </item>
    <item>
      <title>Emulate User Activity with Bots</title>
      <dc:creator>Yoram Kornatzky</dc:creator>
      <pubDate>Sun, 09 Jun 2024 17:26:03 +0000</pubDate>
      <link>https://dev.to/kornatzky/emulate-user-activity-with-bots-5hdd</link>
      <guid>https://dev.to/kornatzky/emulate-user-activity-with-bots-5hdd</guid>
      <description>&lt;p&gt;In our sales events platform, &lt;a href="https://auctibles.com" rel="noopener noreferrer"&gt;Auctibles&lt;/a&gt;, sellers have the exciting opportunity to create various events. These range from a sale event at a fixed price to live auctions, where buyers bid the price up, providing a dynamic and engaging experience. &lt;/p&gt;

&lt;h1&gt;
  
  
  The Technology
&lt;/h1&gt;

&lt;p&gt;We use the Laravel framework in the context of the TALL stack: Tailwind, Alpine.js, Laravel, and Livewire. &lt;/p&gt;

&lt;h1&gt;
  
  
  Why Emulation?
&lt;/h1&gt;

&lt;p&gt;For a demo environment, it's crucial that we accurately emulate buyers'  activity for a seller. This includes sending buy orders and chat messages. The purpose of the emulation is to allow the seller to get a feeling of how the event with multiple buyers will be observed from the event control screens. This includes changing the quantities of items still available, dynamic price changes, and chat messages from buyers.&lt;/p&gt;

&lt;h1&gt;
  
  
  Ordinary Buyers
&lt;/h1&gt;

&lt;p&gt;The buy screen is a Livewire component. Clicking the BUY button sends a broadcast event: &lt;code&gt;LiveBuy,&lt;/code&gt; &lt;code&gt;LiveBid&lt;/code&gt;,... &lt;/p&gt;

&lt;p&gt;Similarly, buyers send chat messages via a broadcast event ChatMessage. &lt;/p&gt;

&lt;h1&gt;
  
  
  Bots for Emulation
&lt;/h1&gt;

&lt;p&gt;We emulate buyers' activity via a bot. The bot is a ReactPHP server that periodically sends the same broadcast events that ordinary buyers send. &lt;/p&gt;

&lt;p&gt;The server receives a &lt;code&gt;set_timer&lt;/code&gt; GET call at the event's start and a &lt;code&gt;stop_timer&lt;/code&gt; GET call at its end. &lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$loop = Factory::create();
$server = new Server(function (ServerRequestInterface $request) use($loop) {

        $path = $request-&amp;gt;getUri()-&amp;gt;getPath();
        $method = $request-&amp;gt;getMethod();

        if ($path == '/set_timer' &amp;amp;&amp;amp; $method === 'GET') {

          $params = $request-&amp;gt;getQueryParams();
                $event_id = $params['event_id'] ?? null;

                // start emulation

                return Response::plaintext('timer set')-&amp;gt;withStatus(Response::STATUS_OK);   

            } else if ($path == '/stop_timer' &amp;amp;&amp;amp; $method === 'GET') {

            $params = $request-&amp;gt;getQueryParams();
                $event_id = $params['event_id'] ?? null;   

                // stop emulation

                return Response::plaintext('timer stopped')-&amp;gt;withStatus(Response::STATUS_OK);               
                    }

});

$socket = new SocketServer('0.0.0.0:' . config('app.BOT_SERVER_PORT'));
$server-&amp;gt;listen($socket);

$loop-&amp;gt;run();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h1&gt;
  
  
  Laravel to ReactPHP
&lt;/h1&gt;

&lt;p&gt;These calls are received from the component where the seller controls the event, using  &lt;code&gt;Http&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  $url = config('app.BOT_SERVER_URL') . ':' . config('app.BOT_SERVER_PORT') . '/set_timer';
  $data = [
  'event_id' =&amp;gt; $this-&amp;gt;event-&amp;gt;id,
  ];

$response = Http::withQueryParameters($data)-&amp;gt;get($url);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h1&gt;
  
  
  Starting the Bot
&lt;/h1&gt;

&lt;p&gt;To start the emulation, we set a periodic timer, &lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$timer = $loop-&amp;gt;addPeriodicTimer($this-&amp;gt;period_in_seconds, function () use ($event_id) {

   $this-&amp;gt;send_bid($event_id);
   $this-&amp;gt;send_message($event_id);

});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;We store the timer in an array:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$this-&amp;gt;timers[$event_id] = $timer;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h1&gt;
  
  
  Stopping the Bot
&lt;/h1&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$loop-&amp;gt;cancelTimer($this-&amp;gt;timers[$event_id]);
unset($this-&amp;gt;timers[$event_id]);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

</description>
      <category>laravel</category>
      <category>ecommerce</category>
      <category>bots</category>
      <category>php</category>
    </item>
    <item>
      <title>Using Disposable Emails for a Demo</title>
      <dc:creator>Yoram Kornatzky</dc:creator>
      <pubDate>Tue, 04 Jun 2024 12:34:40 +0000</pubDate>
      <link>https://dev.to/kornatzky/using-disposable-emails-for-a-demo-130m</link>
      <guid>https://dev.to/kornatzky/using-disposable-emails-for-a-demo-130m</guid>
      <description>&lt;p&gt;We have a demo environment in &lt;a href="https://auctibles.com" rel="noopener noreferrer"&gt;Auctibles&lt;/a&gt;, the dynamic prices and sales events platform. &lt;/p&gt;

&lt;h1&gt;
  
  
  Demo Compared to Production
&lt;/h1&gt;

&lt;p&gt;The demo environment has: &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Emulated shipping that takes 10 minutes in each direction.&lt;/li&gt;
&lt;li&gt;Emulated payment - you click the &lt;code&gt;Pay Now&lt;/code&gt; button to pay, and the payment is immediately executed.&lt;/li&gt;
&lt;/ol&gt;

&lt;h1&gt;
  
  
  Remove Friction in the Demo
&lt;/h1&gt;

&lt;p&gt;Asking people to enter their email for signup will be a central friction point in the demo. To test the demo's functionality, our customer must create several users in our demo environment. &lt;/p&gt;

&lt;p&gt;Users for testing:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;One seller user&lt;/li&gt;
&lt;li&gt;Multiple buyer users &lt;/li&gt;
&lt;li&gt;Optionally team member users - as a seller can operate a team&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The effort to create multiple emails might cause our customers to drop.&lt;/p&gt;

&lt;h1&gt;
  
  
  Disposable Emails
&lt;/h1&gt;

&lt;p&gt;Several providers supply temporary disposable emails that you can use for a few days. So, we decided to allow our customers to use disposable emails in the demo environment. &lt;/p&gt;

&lt;p&gt;So now our signup page has a button to show the customer how to get a disposable email: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn9psow5ygx6kucnfjkcu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn9psow5ygx6kucnfjkcu.png" alt=" " width="800" height="606"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This opens a modal with the list of disposable email providers:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkutsa9qz8vvbgbfekhqx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkutsa9qz8vvbgbfekhqx.png" alt=" " width="800" height="609"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Why Not Allow Disposable Emails in the Production
&lt;/h1&gt;

&lt;p&gt;At the risk of explaining the obvious, a seller or buyer must use the platform for several days to complete the purchase. The seller has to create a sales event, which will be held days before the event. &lt;/p&gt;

&lt;p&gt;After the event, the buyer has to pay and receive the item via delivery. Subsequently, according to consumer protection laws, the buyer is allowed to return the item within 14 days.  &lt;/p&gt;

&lt;p&gt;These disposable emails are usable only for a few days, so they cannot be practically used in production. &lt;/p&gt;

&lt;h1&gt;
  
  
  Cyber Risks
&lt;/h1&gt;

&lt;p&gt;Disposable emails create a cyber risk. It is easy for malicious actors to create disposable emails. &lt;/p&gt;

&lt;p&gt;This is why E-Commerce and SAAS websites often prevent users from using disposable emails. &lt;/p&gt;

</description>
      <category>ecommerce</category>
      <category>demo</category>
      <category>email</category>
      <category>disposable</category>
    </item>
    <item>
      <title>Using ApyHub for Image Moderation</title>
      <dc:creator>Yoram Kornatzky</dc:creator>
      <pubDate>Fri, 31 May 2024 09:05:25 +0000</pubDate>
      <link>https://dev.to/kornatzky/using-apyhub-for-image-moderation-4gg1</link>
      <guid>https://dev.to/kornatzky/using-apyhub-for-image-moderation-4gg1</guid>
      <description>&lt;p&gt;In &lt;a href="https://auctibles.com" rel="noopener noreferrer"&gt;Auctibles&lt;/a&gt;, we use &lt;a href="https://apyhub.com" rel="noopener noreferrer"&gt;ApyHub&lt;/a&gt; to moderate images of items for auction. Auctibles is built using the Laravel PHP framework. &lt;/p&gt;

&lt;h2&gt;
  
  
  Uploading Image Files
&lt;/h2&gt;

&lt;p&gt;Videos are effortlessly uploaded using a straightforward Livewire component and an HTML &lt;code&gt;input&lt;/code&gt; element with &lt;code&gt;type=file&lt;/code&gt;.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;input id="photos" name="photos" type="file" class="sr-only" wire:model.live="photos" accept="image/png,image/jpg,image/gif"&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The component has a public property:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;public $photos = [];
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Upon clicking a &lt;code&gt;submit&lt;/code&gt; button, we apply validation to the field, &lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;'photos' =&amp;gt; 'nullable|array|max:3', // array
'photos.*' =&amp;gt; [
    'required',
    'image',
    'max:10240', // 10MB
    new ExplicitImage(),
],  
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;code&gt;ExplicitImage&lt;/code&gt; is a validation rule.&lt;/p&gt;

&lt;h2&gt;
  
  
  Temporary Files
&lt;/h2&gt;

&lt;p&gt;We upload temporary files to a Minio bucket which resides on the server. The bucket is defined as an &lt;code&gt;uploads&lt;/code&gt; bucket for Laravel. This is done in &lt;code&gt;config/filesystems.php&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  ApyHub Results for Image Content
&lt;/h2&gt;

&lt;p&gt;We use &lt;code&gt;curl&lt;/code&gt; to call ApyHub. The response looks like this:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "data": {
    "apyhub": {
      "adult": {
        "adultScore": 0.0025164163671433926,
        "goreScore": 0.0014069777680560946,
        "isAdultContent": false,
        "isGoryContent": false,
        "isRacyContent": false,
        "racyScore": 0.0032450903672724962
      },
      "metadata": {
        "format": "Png",
        "height": 1024,
        "width": 1024
      }
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
  
  
  The Validation Rule
&lt;/h2&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;?php

namespace App\Rules;

use Closure;
use Illuminate\Contracts\Validation\ValidationRule;
use CURLFile;
use CURLStringFile;
use Illuminate\Support\Facades\Storage;

class ExplicitImage implements ValidationRule
{
    /**
     * Run the validation rule.
     *
     * @param  \Closure(string): \Illuminate\Translation\PotentiallyTranslatedString  $fail
     */
    public function validate(string $attribute, mixed $value, Closure $fail): void
    {
            // path to temporary uploaded file in uploads disk
        $path = config('livewire')['temporary_file_upload']['directory'] . DIRECTORY_SEPARATOR . $value-&amp;gt;getFilename();

        $ch = curl_init();

        curl_setopt(
          $ch,
          CURLOPT_URL,
          "https://api.apyhub.com/ai/image/detect/explicit-content/file"
        );
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
        curl_setopt($ch, CURLOPT_HTTPHEADER, [
          'apy-token: ' . config('app.APYHUB_TOKEN'),
          'content-type: multipart/form-data'
        ]);
        curl_setopt($ch, CURLOPT_POSTFIELDS, [
            'file' =&amp;gt; new CURLStringFile(Storage::disk('uploads')-&amp;gt;get($path), $value-&amp;gt;getFilename(), $value-&amp;gt;getMimeType()),
            'requested_service' =&amp;gt; 'apyhub',
        ]);

        $response = curl_exec($ch);

        curl_close($ch);

        if ($response === false) {
          $error = curl_error($ch);
          logger()-&amp;gt;warning("ApyHub image moderation CURL Error: $error");
          return;
        } else {
          // Process the successful CURL call here.
          $statusCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);

          if ($statusCode == 200) {
            // The request was successful
            $data = json_decode($response, true); //

            $response = $data['data']['apyhub']['adult'];

            if ($response['isAdultContent'] || $response['isGoryContent'] || $response['isRacyContent']) {
                $fail('validation.' . 'explicit_image')-&amp;gt;translate(); 
            }

          } else {
            // The server responded with an error
            logger()-&amp;gt;warning("ApyHub image moderation HTTP Error: $statusCode");
            return;
          }
        }

    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

</description>
      <category>ai</category>
      <category>laravel</category>
      <category>php</category>
      <category>apyhub</category>
    </item>
    <item>
      <title>Use SharpAPI for Translating E-Commerce Product Info</title>
      <dc:creator>Yoram Kornatzky</dc:creator>
      <pubDate>Tue, 28 May 2024 06:38:30 +0000</pubDate>
      <link>https://dev.to/kornatzky/use-sharpapi-for-translating-e-commerce-product-info-40e8</link>
      <guid>https://dev.to/kornatzky/use-sharpapi-for-translating-e-commerce-product-info-40e8</guid>
      <description>&lt;p&gt;In &lt;a href="https://auctibles.com" rel="noopener noreferrer"&gt;Auctibles&lt;/a&gt;, we use &lt;a href="https://sharpapi.com" rel="noopener noreferrer"&gt;SharpAPI&lt;/a&gt; to translate product info. &lt;/p&gt;

&lt;p&gt;Auctibles is built with the Laravel PHP framework.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Service
&lt;/h2&gt;

&lt;p&gt;We defined a service that will translate a text into a given language.&lt;/p&gt;

&lt;p&gt;The service provides a &lt;code&gt;translate&lt;/code&gt; function that translates the &lt;code&gt;text&lt;/code&gt; parameter into the required language:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;?php
namespace App\Services;

use GuzzleHttp\Exception\ClientException;

class SharpAPI
{
    private $service;

    public function __construct()
    {
    }

    public function translate(string $text, string $to_language)
    {       

        try {

            $statusUrl = \SharpApiService::translate($text, $to_language);

        } catch(ClientException $e) {

            $rsp = $e-&amp;gt;getResponse();
            return ['flag' =&amp;gt; null];

        }

        $result = \SharpApiService::fetchResults($statusUrl);
        $res = json_decode($result-&amp;gt;getResultJson());

        return ['flag' =&amp;gt; true, 'content' =&amp;gt; $res-&amp;gt;content];
    }

}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The function returns an associative array, with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;bool flag&lt;/code&gt; - signifying success or failure&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;content&lt;/code&gt; - translated content&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Job
&lt;/h2&gt;

&lt;p&gt;Because calling SharpAPI takes time, we do this asynchronously in a job, once the user saves the product information. &lt;/p&gt;

&lt;p&gt;The job takes an &lt;code&gt;$obj&lt;/code&gt; parameter, which is the product, and an array of &lt;code&gt;$fields&lt;/code&gt; to be translated. The job iterates over the fields and sends each one to the service for translation.&lt;/p&gt;

&lt;p&gt;The object comes from an Eloquent model using &lt;a href="https://spatie.be/docs/laravel-translatable/v6/introduction" rel="noopener noreferrer"&gt;Laravel-Translatable&lt;/a&gt;. So each field is a JSON array, mapping languages to the value for that language.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;?php

namespace App\Jobs\AI;

use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeEncrypted;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;

use SharpAPI\SharpApiService\Enums\SharpApiLanguages;

use App\Services\SharpAPI;

class SharpAPITranslator implements ShouldQueue, ShouldBeEncrypted
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    /**
     * Create a new job instance.
     */
    public function __construct(private string $from_language, private $obj, private $fields)
    {

    }

    /**
     * Execute the job.
     */
    public function handle(): void
    {
            // instantiate the service
        $sharp_api = new SharpAPI();

        foreach($this-&amp;gt;fields as $field) 
        {
                        foreach(array_keys(config('app.content_languages')) as $to_language)
            {
                if ($to_language != $this-&amp;gt;from_language) {

                                        // get the content for the from_language
                    $text = $obj-&amp;gt;getTranslation($field, $fromlang, true)


                    $result = $sharp_api-&amp;gt;translate(
                        $text, 
                        $this-&amp;gt;sharp_api_language($to_language)
                    );


                    if ($result['flag']) {
                            // store the content for to_language
                        $this-&amp;gt;obj-&amp;gt;setTranslation(
                            $field, 
                            $to_language,
                            $result['content']
                        );
                    }


                }
            }

            $this-&amp;gt;obj-&amp;gt;save();

        }

    }

    private function sharp_api_language(string $locale): string
    {
        switch($locale)
        {
            case 'en':
                return SharpApiLanguages::ENGLISH;

            case 'pl':
                return SharpApiLanguages::POLISH;

        }
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

</description>
      <category>laravel</category>
      <category>ai</category>
      <category>php</category>
      <category>ecommerce</category>
    </item>
    <item>
      <title>Integrating a ReactPHP Server in Laravel</title>
      <dc:creator>Yoram Kornatzky</dc:creator>
      <pubDate>Sat, 25 May 2024 10:42:33 +0000</pubDate>
      <link>https://dev.to/kornatzky/integrating-a-reactphp-server-in-laravel-lo0</link>
      <guid>https://dev.to/kornatzky/integrating-a-reactphp-server-in-laravel-lo0</guid>
      <description>&lt;h2&gt;
  
  
  Create a Command
&lt;/h2&gt;

&lt;p&gt;Create a Laravel command, &lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;php artisan make:command SaleServer --command=bidserver:sale
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This command will be a daemon that runs a ReactPHP server.&lt;/p&gt;

&lt;h2&gt;
  
  
  Calling the Server
&lt;/h2&gt;

&lt;p&gt;The command is called with a HTTP &lt;code&gt;post&lt;/code&gt; from a Livewire component, &lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Http::asForm()-&amp;gt;post(config('auctions.SALE_SERVER_URL') . ':' .  config('auctions.SALE_SERVER_PORT') . '/buy', [
    'auction_id' =&amp;gt; $this-&amp;gt;auction-&amp;gt;id,
    'item_id' =&amp;gt; $this-&amp;gt;item-&amp;gt;id,
    'user' =&amp;gt; $this-&amp;gt;user-&amp;gt;id,
    'price' =&amp;gt; $bid_received['price'],
]);   
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
  
  
  The Server
&lt;/h2&gt;

&lt;p&gt;The command creates a ReactPHP server, that receives these calls.&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;?php

namespace App\Console\Commands;


use Illuminate\Console\Command;
use React\Http\Server;
use React\Http\Message\Response;
use React\EventLoop\Factory;
use Psr\Http\Message\ServerRequestInterface;
use React\Socket\SocketServer;

class SaleServer extends Command
{

/**
 * The name and signature of the console command.
 *
 * @var string
 */
protected $signature = 'bidserver:sale';

/**
 * The console command description.
 *
 * @var string
 */
protected $description = 'Sale bid server';

/**
 * Execute the console command.
 */
public function handle()
{
    $loop = Factory::create();
    $server = new Server(function (ServerRequestInterface $request) {

        $path = $request-&amp;gt;getUri()-&amp;gt;getPath();
        $method = $request-&amp;gt;getMethod();

                //Check if the path and method of the call are correct
        if ($path == '/buy'  &amp;amp;&amp;amp; $method === 'POST') {

                    //Extract the call parameters
        $auction_id = $request-&amp;gt;getParsedBody()['auction_id'] ?? null;
        $item_id = $request-&amp;gt;getParsedBody()['item_id'] ?? null;
        $user = $request-&amp;gt;getParsedBody()['user'] ?? null; 
        $bid_price = $request-&amp;gt;getParsedBody()['price'] ?? null; 

          //Broadcast a response
          broadcast(new BuyAccepted($auction_id, $item_id, $user))-&amp;gt;toOthers();

                    return Response::plaintext('bid processed')-&amp;gt;withStatus(Response::STATUS_OK);   

    }             

    return Response::plaintext('Not found')-&amp;gt;withStatus(Response::STATUS_NOT_FOUND);

        });

    $socket = new SocketServer('0.0.0.0:' . config('auctions.SALE_SERVER_PORT'));
    $server-&amp;gt;listen($socket);

  $loop-&amp;gt;run();

}
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
  
  
  Daemon in Forge
&lt;/h2&gt;

&lt;p&gt;Add a daemon running the command:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;php artisan bidserver:sale
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

</description>
      <category>laravel</category>
      <category>php</category>
      <category>reactphp</category>
    </item>
    <item>
      <title>What to Monitor in a Website</title>
      <dc:creator>Yoram Kornatzky</dc:creator>
      <pubDate>Thu, 15 Dec 2022 11:30:42 +0000</pubDate>
      <link>https://dev.to/kornatzky/what-to-monitor-in-a-website-28c6</link>
      <guid>https://dev.to/kornatzky/what-to-monitor-in-a-website-28c6</guid>
      <description>&lt;h2&gt;
  
  
  The Conundrum of Monitoring
&lt;/h2&gt;

&lt;p&gt;So you put up your website in the cloud, and your hosting company, cloud providers, and all sorts of people offer you to monitor your website. &lt;/p&gt;

&lt;h2&gt;
  
  
  Launching A Platform
&lt;/h2&gt;

&lt;p&gt;Now that I am approaching the launch of my platform for auction events for creators, I am putting it up in the cloud. So I need to create a server where the platform will run. Having done that, I encountered so many options to monitor it.&lt;/p&gt;

&lt;p&gt;Despite having twenty-five years of industrial experience, I still found my head swirling from all these offers. In all the IT companies I worked for, we had a DevOps or infrastructure team to monitor whatever we needed.&lt;/p&gt;

&lt;p&gt;So I had to make some order in my thoughts. For this purpose, I created the drawing you see at the top of this article. &lt;/p&gt;

&lt;h2&gt;
  
  
  The Web Application and the Scheduler
&lt;/h2&gt;

&lt;p&gt;We have a web application, a website with screens, and a database. For any platform, there are some jobs that we need to do every day, every week, or every month. Such jobs include sending monthly invoices. Thus, we need a scheduler to run these jobs at the required frequency.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Should We Monitor
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Uptime
&lt;/h3&gt;

&lt;p&gt;We want to know that the website is up and running. This is called &lt;strong&gt;uptime&lt;/strong&gt;. The uptime monitor has to run outside the server and the platform. Because otherwise, you would not know that your server is down. &lt;/p&gt;

&lt;h3&gt;
  
  
  Resources
&lt;/h3&gt;

&lt;p&gt;Websites and servers can get overloaded, either temporarily or permanently. Temporary spikes can be due to particular days of the year, like the holiday shopping season. Permanent overload points to the need to increase the resources available. &lt;/p&gt;

&lt;p&gt;Resource monitors require some agent to be attached to your website to report the CPU, memory, or disk load. The resource monitor can reside inside or outside the server. In many cases, you will use both. An internal monitor is cheaper but can be clogged down in overload. &lt;/p&gt;

&lt;h3&gt;
  
  
  Periodic Jobs
&lt;/h3&gt;

&lt;p&gt;Did the jobs run on time? Were they successfully executed?&lt;/p&gt;

&lt;p&gt;You can record somewhere on the server whether the job is finished. But you must monitor outside the server to see if a job was successfully executed. The reason is that if the job did not run, no one inside could tell you that. Maybe the scheduler is dead.&lt;/p&gt;

&lt;p&gt;So each regular job has to report to an outside monitor on its execution. And the monitor that has a list of jobs and frequencies can compare these reports to what happened and alert you for any malfunction.&lt;/p&gt;

&lt;h2&gt;
  
  
  An External Scheduler
&lt;/h2&gt;

&lt;p&gt;Due to all the complications of scheduling and monitoring, numerous companies offer to schedule jobs for you by calling some endpoint (function) in your platform. Such offers are often named &lt;strong&gt;cron&lt;/strong&gt; something - reminiscent of the Linux &lt;em&gt;cron&lt;/em&gt; scheduler. &lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;I hope that now you can better sort all the different options for monitoring and make a cost-effective choice.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>monitoring</category>
      <category>devops</category>
      <category>cloud</category>
    </item>
    <item>
      <title>The Timeline Method for Determined Construction of Web Applications</title>
      <dc:creator>Yoram Kornatzky</dc:creator>
      <pubDate>Thu, 24 Nov 2022 13:28:47 +0000</pubDate>
      <link>https://dev.to/kornatzky/the-timeline-method-for-determined-construction-of-web-applications-cdg</link>
      <guid>https://dev.to/kornatzky/the-timeline-method-for-determined-construction-of-web-applications-cdg</guid>
      <description>&lt;p&gt;How do you approach the development of a new web application? If you are a solopreneur or SMB, you are not aiming for a big scale. You cannot afford well-known but costly development environments.&lt;/p&gt;

&lt;p&gt;It is a challenge that I encountered repeatedly as a freelance consultant. Build it and ship it without overbuilding.&lt;/p&gt;

&lt;p&gt;Here is the method that I usually use.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Matrix
&lt;/h2&gt;

&lt;p&gt;Think of the complete development process as a two-dimensional plane.&lt;/p&gt;

&lt;p&gt;On the horizontal axis, we have the different phases of the evolution of the web application. This is a timeline of phases that it goes through. &lt;/p&gt;

&lt;p&gt;The vertical time axis is the computing environment you will use.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Time Axis - Phases
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Collection of emails&lt;/li&gt;
&lt;li&gt;Forthcoming - add information pages&lt;/li&gt;
&lt;li&gt;Production - add sign up/sign in and rea; functionality&lt;/li&gt;
&lt;li&gt;Add testimonials and other forms of social proof&lt;/li&gt;
&lt;li&gt;Scale up - multiple servers, load balancing&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Of course, the production phase of development will be broken into multiple sub-phases. &lt;/p&gt;

&lt;p&gt;In the action events platform I am building, we start with notifications via email. Later we will add notifications through SMS.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Environments
&lt;/h2&gt;

&lt;p&gt;Each phase has to go through these environments,&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Local development&lt;/li&gt;
&lt;li&gt;Staging - cloud environment&lt;/li&gt;
&lt;li&gt;Production - production environment&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The reason for going through this sequence is that we need to test things before we release them.&lt;/p&gt;

&lt;p&gt;We start with local development but must move to the cloud to demo anything. &lt;/p&gt;

&lt;h2&gt;
  
  
  Design Backward from Production
&lt;/h2&gt;

&lt;p&gt;We design the production web application in a most general form. Then we define a subset within the general framework for the collection and the forthcoming phase.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Structure
&lt;/h2&gt;

&lt;p&gt;As we would like the collection and forthcoming phases to have the production look and feel, we work by working backward by focusing on the navigation bar and the home page.&lt;/p&gt;

&lt;h3&gt;
  
  
  Navigation Bar
&lt;/h3&gt;

&lt;p&gt;A navigation bar with all pages that will be in production. We expose more and more pages as we proceed through the phases.&lt;/p&gt;

&lt;h3&gt;
  
  
  Home Page
&lt;/h3&gt;

&lt;p&gt;The collection and forthcoming pages have the general look and feel but with an email collection form only. To build a mailing list. &lt;/p&gt;

&lt;p&gt;In the production phase, we move to a real home page.&lt;/p&gt;

&lt;h3&gt;
  
  
  Other Pages
&lt;/h3&gt;

&lt;p&gt;If any other page appears in the collection or forthcoming phase, structure it to represent the corresponding content for that phase if different from the production phase.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;With this approach, you construct one web application for all the phases you encounter. &lt;/p&gt;

&lt;p&gt;Thereby you get a uniform look and feel, with a single simple timeline for development, for all phases. &lt;/p&gt;

</description>
      <category>webdev</category>
      <category>website</category>
      <category>development</category>
    </item>
    <item>
      <title>Experience Continuity in Hybrid Events Platforms</title>
      <dc:creator>Yoram Kornatzky</dc:creator>
      <pubDate>Fri, 04 Nov 2022 11:39:46 +0000</pubDate>
      <link>https://dev.to/kornatzky/experience-continuity-in-hybrid-events-platforms-48d3</link>
      <guid>https://dev.to/kornatzky/experience-continuity-in-hybrid-events-platforms-48d3</guid>
      <description>&lt;p&gt;You are building a hybrid events platform, such as the Auction Events Platform for Creators I am making. Users will be on and off the website during the event. How do you achieve continuity of the experience for your users?&lt;/p&gt;

&lt;h1&gt;
  
  
  Define Meaningful Moments
&lt;/h1&gt;

&lt;p&gt;Choose moments when there is some phase change, such as the start of a session or lecture. In my auction platform, this is when an item (lot) comes up for auction during a live auction. &lt;/p&gt;

&lt;p&gt;Broadcast these meaningful events to users via email, SMS, etc., so that users that are off the website know about it. &lt;/p&gt;

&lt;h1&gt;
  
  
  Define Sub Moments
&lt;/h1&gt;

&lt;p&gt;In many event platforms, such moments are spread in time for an hour or more, during which time meaningful things happen. &lt;/p&gt;

&lt;p&gt;In my auction events platform, we have &lt;strong&gt;timed auctions&lt;/strong&gt; that run automatically from start time to end time. In a timed auction, all items come up for auction at the start time. An auction can last for days. So a user might bid and then go off to his/her life. You need to tell the user when a competing bid comes up and is on top. So the user can come back and bid again at a higher price. &lt;/p&gt;

&lt;p&gt;In a conference session, such sub-moments could be the switch to Q&amp;amp;A at the end of a presentation. In a concert platform, a sub-moment is when the band starts to play a new song. The way Apple Music on a Mac shows a notification when a new song start. &lt;/p&gt;

&lt;h1&gt;
  
  
  Detect In-Person Presence
&lt;/h1&gt;

&lt;p&gt;In a hybrid event, you want to turn off notifications for users that present IRL (in real life), as these notifications will distract them from what is happening IRL.&lt;/p&gt;

&lt;p&gt;In the auction events platform, users present in person will be given a sign with their user number with which to bid. We will use the opportunity to record their presence on the platform and avoid sending notifications to them.&lt;/p&gt;

&lt;h1&gt;
  
  
  Distinguish Pre-Event and Post-Event Phases
&lt;/h1&gt;

&lt;p&gt;In many event platforms and webinars, there will be interaction before and after the event. Such as chatting with organizers or speakers. In our auction events platform, users can chat with creators before, during, and after the live auction. &lt;/p&gt;

&lt;p&gt;In the pre-event and post-event phases, you can expect users to be off the website for extended periods. And messages may come at any time. So we need to forward any message to users with a high probability of not being online in the platform via email or SMS. &lt;/p&gt;

&lt;p&gt;During a live auction, such forwarding is not needed and is distracting from the attention on the bidding. &lt;/p&gt;

&lt;h1&gt;
  
  
  Conculsion
&lt;/h1&gt;

&lt;p&gt;By now, most of us have participated in some virtual or hybrid conference and received a barrage of email messages around it. The snowball effect is jarring and counter-productive.&lt;/p&gt;

&lt;p&gt;Micro-designing notifications and messaging from the standpoint of experience continuity promises a much more pleasant user experience and will lead to higher engagement.  &lt;/p&gt;

</description>
      <category>auctions</category>
      <category>hybridevent</category>
      <category>realtimeweb</category>
      <category>userinterface</category>
    </item>
    <item>
      <title>Generic Design for a Creator Economy Platform</title>
      <dc:creator>Yoram Kornatzky</dc:creator>
      <pubDate>Wed, 05 Oct 2022 10:24:09 +0000</pubDate>
      <link>https://dev.to/kornatzky/generic-design-for-a-creator-economy-platform-jej</link>
      <guid>https://dev.to/kornatzky/generic-design-for-a-creator-economy-platform-jej</guid>
      <description>&lt;p&gt;You want to build a creator economy platform. It could be a newsletters platform, like Revue or Substack, a podcasting platform like Anchor or Transistor, or a crowd-funding platform, like Patreon or Buy Me Coffee.&lt;/p&gt;

&lt;p&gt;How do you design such a platform? There are many moving parts and infinite possibilities. Yet, after studying many creator economy platforms, we constructed a generic design you can use to build your platform.&lt;/p&gt;

&lt;h1&gt;
  
  
  The Two Faces of Creator Economy Platform
&lt;/h1&gt;

&lt;p&gt;A creator economy platform wears two faces:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A consumer-facing face - the link the creator gives to its fans to see the profile, newsletter, podcast, etc.&lt;/li&gt;
&lt;li&gt;A creator-facing face - the interface through which the creator creates content, such as uploading a new podcast, writing the profile, and tweaking the consumer-facing face&lt;/li&gt;
&lt;/ol&gt;

&lt;h1&gt;
  
  
  The Consumer-Facing Face
&lt;/h1&gt;

&lt;p&gt;The consumer-facing face is a website, possibly with a custom domain or custom subdomain.&lt;/p&gt;

&lt;p&gt;The website presents the creator and its activities. Its specific content will be defined by what the creator economy platform is doing. &lt;/p&gt;

&lt;p&gt;For a newsletters platform, the website will present the newsletter information, the creator information, and a listing of newsletter issues. The front page will include a call-to-action (CTA) for a subscription to the newsletter.&lt;/p&gt;

&lt;p&gt;Generally, this website is either a one-pager or has two levels:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Creator information and list of creations (e.g., newsletter issues, podcast episodes)&lt;/li&gt;
&lt;li&gt;A page for each creation (e.g., podcast episode)&lt;/li&gt;
&lt;/ol&gt;

&lt;h1&gt;
  
  
  The Creator-Facing Face
&lt;/h1&gt;

&lt;p&gt;The creator-facing face is foremost a website builder - for generating and tweaking the consumer-facing face.&lt;/p&gt;

&lt;p&gt;This includes several generic components. &lt;/p&gt;

&lt;h2&gt;
  
  
  Profile Creator
&lt;/h2&gt;

&lt;p&gt;The profile editor sets creator details.&lt;/p&gt;

&lt;p&gt;The profile creator tweaks the website theme and style and provides a setup for the essential assets for the website, such as the profile photo and background. &lt;/p&gt;

&lt;h2&gt;
  
  
  Creation Board
&lt;/h2&gt;

&lt;p&gt;The board is where you create content. It includes:&lt;/p&gt;

&lt;h3&gt;
  
  
  Details Form
&lt;/h3&gt;

&lt;p&gt;A form with inputs for creation name and additional details, and panels for uploading assets, such as cover images&lt;/p&gt;

&lt;h3&gt;
  
  
  Rich Text Editor
&lt;/h3&gt;

&lt;p&gt;Most creations involve some long-form text. For a newsletters platform, it is a newsletter editor. For a podcast platform, it is an episode editor. &lt;/p&gt;

&lt;h3&gt;
  
  
  Rich Media Interface
&lt;/h3&gt;

&lt;p&gt;Most creations are media, such as audio for podcasts or video for courses. The rich media interface is where you upload the media. &lt;/p&gt;

&lt;p&gt;Through the interface, you may further process media, such as changing formats. &lt;/p&gt;

&lt;h2&gt;
  
  
  Integrations Setup
&lt;/h2&gt;

&lt;p&gt;For most creator economy platforms, the website generated is just the presentation layer. The content has to be distributed from the platform to the consumer. If it is a newsletter, you need to mail it. If it is a podcast, you need to distribute it to podcast platforms such as Spotify or Apple Podcasts.&lt;/p&gt;

&lt;p&gt;Hence each creator economy platform needs a section of specific integration to enable this distribution for the creator. We cannot give a generic design here. &lt;/p&gt;

&lt;h1&gt;
  
  
  Conclusions
&lt;/h1&gt;

&lt;p&gt;Hopefully, our generic design will kickstart the design of your platform. &lt;/p&gt;

</description>
      <category>creatoreconomy</category>
      <category>websitebuilder</category>
      <category>website</category>
      <category>websitedesign</category>
    </item>
    <item>
      <title>Custom Domain Support in Creator Economy Platforms</title>
      <dc:creator>Yoram Kornatzky</dc:creator>
      <pubDate>Fri, 23 Sep 2022 16:10:35 +0000</pubDate>
      <link>https://dev.to/kornatzky/custom-domain-support-in-creator-economy-platforms-2hl0</link>
      <guid>https://dev.to/kornatzky/custom-domain-support-in-creator-economy-platforms-2hl0</guid>
      <description>&lt;p&gt;Most creator platforms support two forms of custom domains: \&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Subdomains - such as &lt;em&gt;johndoe.platformX.com&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;Custom domain - such as &lt;em&gt;platformX.johndoe.com&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Describing the creator domain as part of the URL of the site, such as &lt;em&gt;&lt;a href="http://www.platformX.com/johndoe" rel="noopener noreferrer"&gt;www.platformX.com/johndoe&lt;/a&gt;&lt;/em&gt;, is much less common.&lt;/p&gt;

&lt;p&gt;If you are about to construct such a platform, you may wonder how complicated it is to build custom domain support. &lt;/p&gt;

&lt;p&gt;It turns out it is straightforward. &lt;/p&gt;

&lt;p&gt;It boils down to two things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The user doing setup in their domain registrar or website hosting company&lt;/li&gt;
&lt;li&gt;Routing in your platform&lt;/li&gt;
&lt;/ol&gt;

&lt;h1&gt;
  
  
  Domain Setup by User
&lt;/h1&gt;

&lt;p&gt;You should instruct users to add appropriate DNS records in their domain registrar or hosting company. There are many examples of instructions in existing platforms that you can borrow. &lt;/p&gt;

&lt;p&gt;The &lt;a href="https://support.transistor.fm/en/article/using-a-custom-domain-for-your-podcast-website-ivsoyr/" rel="noopener noreferrer"&gt;Transistor podcasting platform&lt;/a&gt; instructions that I recently used are excellent.&lt;/p&gt;

&lt;h1&gt;
  
  
  Routing in Platform
&lt;/h1&gt;

&lt;p&gt;While I do not know which backend programming framework or language you use, the following in &lt;a href="https://laravel.com" rel="noopener noreferrer"&gt;Laravel&lt;/a&gt; PHP is pretty generic and can be adapted to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Any PHP framework&lt;/li&gt;
&lt;li&gt;Express.js or any Node.js backend framework&lt;/li&gt;
&lt;li&gt;Ruby on Rails&lt;/li&gt;
&lt;li&gt;Java Spring Boot&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Domains
&lt;/h2&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Route::group(['domain' =&amp;gt; '{subdomain}.{domain}.{tld}'], function(){

    Route::any('/', function($sub, $domain, $tld){
        return 'custom domain: ' .  
            $sub . '. ' . 
            $domain .  '. ' . 
            $tld;
    });

});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
  
  
  Subdomain
&lt;/h2&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Route::domain('{account}.example.com')-&amp;gt;group(function () {
    Route::get('user/{id}', function ($account, $id) {
        //
    });
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

</description>
      <category>laravel</category>
      <category>domain</category>
      <category>php</category>
      <category>dns</category>
    </item>
    <item>
      <title>Integrating Services in Website Platforms</title>
      <dc:creator>Yoram Kornatzky</dc:creator>
      <pubDate>Tue, 20 Sep 2022 15:26:03 +0000</pubDate>
      <link>https://dev.to/kornatzky/integrating-services-in-website-platforms-iij</link>
      <guid>https://dev.to/kornatzky/integrating-services-in-website-platforms-iij</guid>
      <description>&lt;h1&gt;
  
  
  Website Platforms
&lt;/h1&gt;

&lt;p&gt;Website platforms include website builders, Link In Bio, and client portals, among others. &lt;/p&gt;

&lt;p&gt;Interaction with users, including calendar appointments, video calls, and on-site chat, is now considered standard practice on websites providing any service. &lt;/p&gt;

&lt;h1&gt;
  
  
  Integrate or Build
&lt;/h1&gt;

&lt;p&gt;When building website platforms, you can consider several approaches to include such services. &lt;/p&gt;

&lt;p&gt;You can:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Build the integration yourself&lt;/li&gt;
&lt;li&gt;Using some widget for service authorization, like Google Calendar integration&lt;/li&gt;
&lt;li&gt;Use an integration service, such as &lt;a href="https://www.nylas.com" rel="noopener noreferrer"&gt;Nylas&lt;/a&gt;, for calendar integration&lt;/li&gt;
&lt;li&gt;Use an infrastructure service, such as &lt;a href="https://www.agora.io/en/" rel="noopener noreferrer"&gt;Agora&lt;/a&gt;, for video/audio calls&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Your choice from a cost perspective is derived from:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Cost of building the integration or service yourself&lt;/li&gt;
&lt;li&gt;Cost of the integration service or cost of service.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;From a platform architecture and user experience, the tradeoffs are clear. Building your system and integrations is much more work. Yet, you have more control over the experience. &lt;/p&gt;

&lt;p&gt;However, there is an additional usability and cost aspect to be considered.&lt;/p&gt;

&lt;h1&gt;
  
  
  Do Users Arrive Bare Naked?
&lt;/h1&gt;

&lt;p&gt;If your users already have subscriptions to &lt;a href="https://zoom.us" rel="noopener noreferrer"&gt;Zoom&lt;/a&gt; and &lt;a href="https://calendly.com" rel="noopener noreferrer"&gt;Calendly&lt;/a&gt; and are used to them, then moving to another service, either through a building by yourself or integration, is cumbersome. &lt;/p&gt;

&lt;p&gt;First, you need to bill them somehow for something they are already paying for it or an equivalent somewhere else. This would make your pricing not convenient and may hamper user activation. &lt;/p&gt;

&lt;p&gt;Second, you force your users to move from a service they are used to something new. You are again hampering user activation.&lt;/p&gt;

&lt;h1&gt;
  
  
  Where is the Cross Line?
&lt;/h1&gt;

&lt;p&gt;It is tough to make available projections, but professional users, such as physicians, accountants, and lawyers, would appreciate a website platform that provides all services within, without the need to bring their service subscriptions.&lt;/p&gt;

&lt;p&gt;IT technology professionals tend to prefer bringing their services. &lt;/p&gt;

&lt;p&gt;Creators tend to fall somewhere in the middle, with a propensity to prefer a fully rounded platform with the need to bring anything with them. &lt;/p&gt;

&lt;h1&gt;
  
  
  Website Platform for Professionals
&lt;/h1&gt;

&lt;p&gt;We are building a website platform for professionals that provides:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Appointment booking&lt;/li&gt;
&lt;li&gt;Real-time chat&lt;/li&gt;
&lt;li&gt;Video/Audio calls&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;All are fully provided by the platform. &lt;/p&gt;

&lt;p&gt;We believe we should provide a complete solution in one subscription. &lt;/p&gt;

</description>
      <category>websitebuilde</category>
      <category>websitedev</category>
      <category>zoom</category>
      <category>calendly</category>
    </item>
  </channel>
</rss>
