Category: Web-development

  • How to add alert notifications in your app, the easy way.

    How to add alert notifications in your app, the easy way.

    It’s such a beautifully, useful and simple thing that I had to write about it…

    Sooner or later your app needs to tell you something: a CRON job failed, some queue is backing up, a quota is almost gone, a big customer just signed up. You don’t need a paid service, an SDK, or the joy of debugging email deliverability for this. A Slack Incoming Webhook is a secret URL you POST JSON to, and that’s the whole trick.

    Step 1 – Create the webhook

    In Slack: create an app (api.slack.com/apps > Create New App > From scratch), turn on Incoming Webhooks, then Add New Webhook to Workspace and pick a channel. You get back a URL like:

    https://hooks.slack.com/services/T00000000/B00000000/xxxxxxxxxxxxxxxxxxxxxxxx

    That URL is the credential. You don’t need anything else, no secrets, no IDs, nothing – those are for other Slack stuff. For “post a message to this channel”, the URL is enough.

    Step 2 – Store it as a secret

    Drop it in your environment, never in the repo:

    # .env  (git-ignored)
    SLACK_WEBHOOK_URL="https://hooks.slack.com/services/T00000000/B00000000/xxxx"

    Step 3 – A tiny reusable helper

    A few lines of code, no deps, and – importantly – best-effort: meaning a notification that fails will not take your app down with it.

    // notify.ts
    
    type NotifyOpts = { 
      title?: string; 
      level?: "info" | "warn" | "error" 
    };
    
    const EMOJI = { 
      info: "ℹ️", 
      warn: "⚠️", 
      error: "🚨" 
    } as const;
    
    export async function notify(
      text: string, 
      opts: NotifyOpts = {}
    ): Promise<void> {
      const url = process.env.SLACK_WEBHOOK_URL;
      if (!url) return; // no-op, don't blow up
    
      const emoji = EMOJI[opts.level ?? "info"];
      const heading = opts.title 
        ? `${emoji} *${opts.title}*\n`
        : "";
    
      try {
        await fetch(url, {
          method: "POST",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify({ text: heading + text }),
        });
      } catch (err) {
        console.error("notify failed (non-fatal):", err);
      }
    }

    Use it anywhere

    await notify("Mr. Dude, stuff happened! ✅");
    
    await notify(
      `DB usage is at ${percent}% of the plan cap.`, {
        title: "Quota warning",
        level: "warn",
      });

    That’s a working alert system. Really.

    Want it to look nicer?

    Slack is really good at this stuff; the messages can carry Block Kit blocks for headings and compact key/value grids. Keep the top-level text too, it’s the fallback used in push notifications:

    body: JSON.stringify({
      text: "Quota warning", // fallback
      blocks: [
        { 
          type: "section", 
          text: { 
            type: "mrkdwn", 
            text: "🚨 *Quota warning*" 
          } 
        },
        { type: "section", fields: [
          { type: "mrkdwn", text: "*Used:*\n84%" },
          { type: "mrkdwn", text: "*Plan:*\nScaler" },
        ]},
      ],
    })

    Two rules, and you’re done

    1. Best-effort, always. Wrap the send in try/catch and do a no-op when the URL is missing. Your alerting is the least important thing in any request; it must never be the thing that breaks it.
    2. The URL is a secret. Anyone who has it can post to your channel. Keep it out of git, and if it leaks, regenerate it in Slack (Incoming Webhooks > remove/add); no code changes, just swap the environment variable.

    BTW, same pattern works for Discord and Telegram if Slack isn’t your thing; a different URL and a slightly different JSON body, basically same code; or use Python if you’re a guru :-).

    I’m honestly wondering if there’s an easier way. I’d probably use it.

    That’s it.
    Enjoy!


    Slack logo rendered with Krbn.

  • Framework-agnostic, ad-hoc image cropper

    Framework-agnostic, ad-hoc image cropper

    Today, I’m releasing a framework-agnostic, in-browser image cropper. The motivation is that I need this on a regular basis in multiple projects, across multiple frameworks and – while I’m aware of existing tools like Cropper.js and others – quite frankly, to me they seem too complex to setup especially depending on the front-end framework I’m dealing with. I needed this problem reduced to a simple JS call.

    (more…)
  • Dynamic session cookie name in Laravel

    Here’s a case that may be useful when authenticating multiple apps from a single Laravel code-base.

    Recently I’ve built an application API and admin UI in the same Laravel code-base. This made lots of things easier to manage, including DB-related stuff. The API is consumed by a single-page app (ReactJS) and the backend is built with Laravel Orchid.

    Also, it’s worth noting that the API is using Laravel Sanctum to provide session-based authentication to the front-end application.

    The problem

    Because both the front-end app (ReactJS+API) as well as the admin interface (Orchid) are using session-based authentication, and both are served from the same Laravel code-base, the session cookie name is the same in both cases, which means that sessions created in one app are automatically read by the other, which will obviously fail, because the apps have entirely different user accounts.

    Trying to solve this thing proved to be more difficult than I thought, since Laravel is built from the ground up to use the configured session cookie name (i.e. session.cookie defined in config/session.php).

    What makes things worse is that Laravel creates the session on the handled request before any middleware gets executed, and the created session already has the initially configured name set.

    The solution

    After several hours of trials, here’s what worked, in short:

    • Created a middleware called session.tenant that takes a string parameter (a tenant name).
    • In this middleware, I set the value of the config setting session.cookie but also the run-time session name (i.e. Session::setName()).
    • In Kernel.php I prepended the session.tenant:admin middleware to the admin UI routes (basically the web group) and the session.teant:api to the API routes (the api group).

    It might not be the most elegant approach, but these changes proved to work very well with minimal code and without touching any of Laravel’s internals. Checkout the code below for details.

    app/Http/Middleware/SessionTenant.php

    <?php
    
    namespace App\Http\Middleware;
    
    use Closure;
    use Illuminate\Http\Request;
    use Illuminate\Support\Facades\Config;
    use Illuminate\Support\Facades\Session;
    
    class SessionTenant
    {
        /**
         * Handle an incoming request.
         *
         * @param  \Illuminate\Http\Request  $request
         * @param  \Closure  $next
         * @return mixed
         */
        public function handle(Request $request, Closure $next, string $tenant)
        {
            $original = Session::getName();
            $modified = "{$original}_{$tenant}";
    
            Session::setName($modified);
            Config::set('session.cookie', $modified);
    
            return $next($request);
        }
    }

    app/Http/Kernel.php

    I’m only showing the relevant additions to the Kernel.php file, the rest is just the usual Laravel code.

    # ...
    
    protected $middlewareGroups = [
        'web' => [
            'session.tenant:platform',
            # ...
        ],
        'api' => [
            'session.tenant:api',
            # ...
        ],
    ];
    
    # ...
    
    protected $routeMiddleware = [
        # ...
        'session.tenant' => \App\Http\Middleware\SessionTenant::class,
    ];
    
    public function __construct(Application $app, Router $router)
    {
        parent::__construct($app, $router);
        $this->prependToMiddlewarePriority(\App\Http\Middleware\SessionTenant::class);
    }

    Note also the call to $this->prependToMiddlewarePriority(...) in the constructor (at the end). This pushes the middleware at the start of the processing chain so the session name is changed as soon as possible.

    Hope it helps, enjoy!

  • Dynamic custom data via OAuth with Laravel Socialite

    If you’ve ever done authentication in a Laravel-based project then you probably had to deal with the Socialite extension, which enables OAuth-based authentication using third-party services (like Google or Facebook).

    Passing dynamic data to the external OAuth system and getting that data back to your application, using Socialite, is something neither easily done nor well documented and given that I’ve seen a lot of people wasting time on this, I want to try and shed some light on it here.

    (more…)
  • Loomiere 2.0.1-beta finally out!

    All right! I finally got around to releasing this code to the public. I won’t say much now (since I’m tired) but I will say that this software has been functioning in production environment for over 1 year and a half serving all the video content available on the romanian website http://peteava.ro (about 800 TB of data each month out of 14 servers).

    Some (old) statistics showing it’s power can be found here.

    Source code: https://github.com/valeriupalos/loomiere.

    Warning:
    Any software downloaded from this website is *never* to be associated in any form with pornographic or erotic content! I.E. “Loomiere” must *never* be used to stream pornographic or erotic videos! There are plenty alternatives if you can’t help it.

  • Loomiere/Stream: Revived!

    UPDATE: /1165/loomiere-2-0-1-beta-finally-out/

    OK folks, things have settled down and we are good to go. After some considerations the legal concerns for Loomiere/Stream are now cleared and gone. The source is now released and will be available indefinitely. Again, this streamer (minimally customized) has already been serving all the video content at http://peteava.ro for 3 full months (for those seeking a demo).

    Source code: loomiere-0.2.1-tar.gz

    Warning:
    Any software downloaded from this website is *never* to be associated in any form with pornographic or erotic content! I.E. “Loomiere/Stream” must *never* be used to stream pornographic or erotic videos! There are plenty alternatives if you can’t help it.

    Just a teaser:
    Fighting the video-streaming problem has taught me very many things which, in turn, led me to realize that I might be able to use a substantially different streaming approach to achieve a massive amount of optimization in this field. So quite soon, I think I’ll have ready a brand new (and far more powerful) streamer that aims at making it possible for a single server to serve many thousands (I am still testing this) of streams simultaneously using commodity hardware. I am not yet settled on whether this project will be commercial, open-sourced or both but I hope to clear this aspect soon as well. Real-world streaming (i.e. before and after) statistics will be made available on release.

    Stay tuned! 🙂

  • @font-face for a programmer’s blog?

    I’m sure that many of you are well aware of the exceptional @font-face CSS3 rule, which made life much easier for many designers, I would think. A few days ago I started wondering if it would be appropriate to use such an instrument on my blog… the site you’re on! Yoopie!

    And, why not!? I love the idea of making my source codes more readable by using some custom font (Consolas in action). Anyway, I started looking around for some fonts and was not pleased with any of them (well to be fair I did like DejaVu Sans Mono but not as much as Consolas, unfortunately).
    (more…)