Заметки https://t-jo.com малого бизнеса Sun, 12 Oct 2025 22:15:40 +0000 ru-RU hourly 1 https://wordpress.org/?v=6.8.3 🧭 Guide: Building 4 synced SVG shapes that morph into one smiley (no JS) https://t-jo.com/guide-building-4-synced-svg-shapes-that-morph-into-one-smiley-no-js.html https://t-jo.com/guide-building-4-synced-svg-shapes-that-morph-into-one-smiley-no-js.html#respond Sun, 12 Oct 2025 22:03:15 +0000 https://t-jo.com/?p=5794 What we’re building

 

  • A thin equilateral triangle outline in the background that rotates.

  • Three shapes placed at the triangle’s vertices: a regular 5-point star, a regular (equilateral) triangle, and a square.

  • All three shapes morph into the same perfect circle (a smiley face via a mask) and back, in sync with the rotation and mask timing.

  • All shape contours live in HTML (inside <path d=»…»> and <animate>), no JS, CSS only for styling and timing of visibility/spin.

 

Why it works (big ideas)

 

  1. Unified topology for morphing

    Browsers morph SVG paths by interpolating numbers in the d attribute. For a clean morph, each path must have the same “shape grammar”: same number/order of commands and numbers.

    We enforce this by describing every state (star, triangle, square, circle) as a single path: M + 12 cubic C segments + Z.

    Straight lines (square/triangle edges, star arms) are simply degenerate cubics (control points on the line), so they still fit the same “12 × C” pattern.

  2. A perfect-looking circle

    The target “one shape” is a circle centered at (50,50) with radius 30. We render it with 12 cubic Bézier arcs (30° each) so it’s smooth (no polygon facets).

  3. Mask for the smiley face

    Eyes and mouth are cut out with a <mask>. We don’t morph them — we fade the holes on only during the circle phase (so they move in sync with the morph).

  4. Global sync

    • All SMIL morphs (<animate attributeName=»d»>) use the exact same duration and keyTimes.

    • The CSS animations (mask visibility and orbit spin) use the same total duration and linear timing.

    • Result: everything switches phases at the same instants.

     

  5. Safe layout

    • The “stage” is 80vmin so it always fills 80% of the smaller viewport side.

    • The revolving centers sit on a radius that keeps shapes inside the viewBox while rotating.

     

 


 

Step-by-step (kid-friendly)

 

  1. Make a stage

    Create an SVG that fills a square area (viewBox=»0 0 100 100″) and put it in a centered container sized 80vmin. That keeps things proportional on any screen.

  2. Add a rotating triangle frame

    Draw a thin equilateral triangle outline around the center. Put the triangle and your shapes in a <g id=»orbit»> and rotate that group with CSS. Now the triangle and shapes spin together.

  3. Place the 3 shapes at the triangle’s corners

    We use three <path>s: star, triangle, square. Their centers are exactly at the frame’s three vertices. Sizes are chosen to look balanced and never clip.

  4. Describe each shape with the same “grammar”

    Each <path> starts with M, then 12 cubic C segments, then Z.

    Even straight edges are drawn with cubic segments whose control points are on the line (so it stays straight). That keeps morphing stable and pretty.

  5. Define the target circle

    The circle (smiley body) is drawn as 12 cubic arcs (30° per arc). That gives a visually perfect circle.

  6. Morph the shapes into the circle and back

    On each path, add <animate attributeName=»d»> with:

    • dur=»11.2s» total;

    • hold 5s on the start shape → morph 0.6shold 5s on circle → morph 0.6s back;

    • implemented with keyTimes=»0;0.4464286;0.5;0.9464286;1″ (those are 5/11.2, 0.6/11.2, etc.).

      All three use identical timings.

     

  7. Cut eyes and mouth only during circle phase

    Create a <mask> with two circles (eyes) and a curved path (mouth).

    Use a CSS animation on the mask group’s opacity with the same 11.2s duration and the same phase splits so the holes show only in the circle phase.

  8. Style once with CSS

    Use currentColor to fill all shapes using a single —orange variable. Keep comments clear. Respect prefers-reduced-motion.

  9. Test sync

    • On load, everything starts at t=0.

    • After ~5s, there’s a 0.6s morph into the circle, the holes appear, the orbit keeps rotating smoothly.

    • After another 5s, there’s a 0.6s morph back.

    • All events cross the same timestamps → no phase drift.

     

 


 

Troubleshooting checklist

 

  • One shape lags behind? Double-check all durations are 11.2s and keyTimes are identical across all <animate>and CSS @keyframes.

  • Edges look polygonal? Make sure the circle is the cubic-Bézier version (not polygon points).

  • Anything clips the edges? Keep the orbit centers radius and shape sizes as provided.

  • Motion sensitivity? The code respects prefers-reduced-motion: reduce to disable CSS animations.

 


 

✅ Production code for CodePen

 

Paste the next block into HTML and the second one into CSS.

Only comments, <title>, and <desc> are translated to English. Paths, numbers, timings remain the same.

🔸 HTML

Exapmle of Morph

<main class="stage"><main class="stage"> <svg viewBox="0 0 100 100" role="img" aria-labelledby="t d" focusable="false"> <title id="t">4 synced shapes → one smiley circle (morphing)</title> <desc id="d"> Background: a thin equilateral triangle outline rotates. At its vertices sit: a star, an equilateral triangle, and a square. All three morph in sync into a single circle with a smiley (mask), then back. All contours are defined in HTML. No JS. </desc> <defs> <!-- Smiley mask: white = visible, black = holes (eyes/mouth) --> <mask id="smileMask"> <rect x="0" y="0" width="100" height="100" fill="white" /> <!-- Eyes + mouth: we show them only during the circle phase via CSS opacity animation --> <g id="smileHoles" opacity="0"> <!-- Eyes --> <circle cx="43" cy="45" r="3" fill="black" /> <circle cx="57" cy="45" r="3" fill="black" /> <!-- Mouth --> <path d="M40 58 Q50 68 60 58" fill="none" stroke="black" stroke-width="5" stroke-linecap="round" /> </g> </mask> </defs> <!-- The rotating "orbit": background triangle + 3 morphing shapes at its vertices --> <g id="orbit"> <!-- Thin equilateral triangle outline (1px, non-scaling stroke), same group so it spins in sync --> <!-- Vertices: (50,24) (72.5,63) (27.5,63) — radius from center is 26 --> <path id="wireTri" d="M50 24 L72.5 63 L27.5 63 Z" fill="none" stroke="currentColor" stroke-width="1" vector-effect="non-scaling-stroke" /> <!-- 1) STAR (bottom-left, center near 27.5,63) → CIRCLE(center 50,50; R=30) → STAR --> <path id="star" class="shape" mask="url(#smileMask)" d="M27.483 54 C27.82 55.036 28.157 56.073 28.494 57.109 C28.83 58.146 29.167 59.182 29.504 60.219 C31.684 60.219 33.863 60.219 36.043 60.219 C34.279 61.5 32.516 62.781 30.753 64.062 C31.426 66.135 32.1 68.208 32.773 70.281 C31.01 69 29.247 67.719 27.483 66.438 C26.602 67.078 25.72 67.719 24.838 68.359 C23.957 69 23.075 69.641 22.193 70.281 C22.867 68.208 23.54 66.135 24.214 64.062 C22.451 62.781 20.687 61.5 18.924 60.219 C21.103 60.219 23.283 60.219 25.463 60.219 C26.136 58.146 26.81 56.073 27.483 54 Z"> <!-- Morph timing: total 11.2s → hold 5s (0–44.64286%) → morph 0.6s (to 50%) → hold 5s → morph back 0.6s --> <animate attributeName="d" dur="11.2s" repeatCount="indefinite" calcMode="linear" keyTimes="0;0.4464286;0.5;0.9464286;1" values=" M27.483 54 C27.82 55.036 28.157 56.073 28.494 57.109 C28.83 58.146 29.167 59.182 29.504 60.219 C31.684 60.219 33.863 60.219 36.043 60.219 C34.279 61.5 32.516 62.781 30.753 64.062 C31.426 66.135 32.1 68.208 32.773 70.281 C31.01 69 29.247 67.719 27.483 66.438 C26.602 67.078 25.72 67.719 24.838 68.359 C23.957 69 23.075 69.641 22.193 70.281 C22.867 68.208 23.54 66.135 24.214 64.062 C22.451 62.781 20.687 61.5 18.924 60.219 C21.103 60.219 23.283 60.219 25.463 60.219 C26.136 58.146 26.81 56.073 27.483 54 Z; M27.483 54 C27.82 55.036 28.157 56.073 28.494 57.109 C28.83 58.146 29.167 59.182 29.504 60.219 C31.684 60.219 33.863 60.219 36.043 60.219 C34.279 61.5 32.516 62.781 30.753 64.062 C31.426 66.135 32.1 68.208 32.773 70.281 C31.01 69 29.247 67.719 27.483 66.438 C26.602 67.078 25.72 67.719 24.838 68.359 C23.957 69 23.075 69.641 22.193 70.281 C22.867 68.208 23.54 66.135 24.214 64.062 C22.451 62.781 20.687 61.5 18.924 60.219 C21.103 60.219 23.283 60.219 25.463 60.219 C26.136 58.146 26.81 56.073 27.483 54 Z; M50 20 C55.266 20 60.439 21.386 65 24.019 C69.561 26.652 73.348 30.439 75.981 35 C78.614 39.561 80 44.734 80 50 C80 55.266 78.614 60.439 75.981 65 C73.348 69.561 69.561 73.348 65 75.981 C60.439 78.614 55.266 80 50 80 C44.734 80 39.561 78.614 35 75.981 C30.439 73.348 26.652 69.561 24.019 65 C21.386 60.439 20 55.266 20 50 C20 44.734 21.386 39.561 24.019 35 C26.652 30.439 30.439 26.652 35 24.019 C39.561 21.386 44.734 20 50 20 Z; M50 20 C55.266 20 60.439 21.386 65 24.019 C69.561 26.652 73.348 30.439 75.981 35 C78.614 39.561 80 44.734 80 50 C80 55.266 78.614 60.439 75.981 65 C73.348 69.561 69.561 73.348 65 75.981 C60.439 78.614 55.266 80 50 80 C44.734 80 39.561 78.614 35 75.981 C30.439 73.348 26.652 69.561 24.019 65 C21.386 60.439 20 55.266 20 50 C20 44.734 21.386 39.561 24.019 35 C26.652 30.439 30.439 26.652 35 24.019 C39.561 21.386 44.734 20 50 20 Z; M27.483 54 C27.82 55.036 28.157 56.073 28.494 57.109 C28.83 58.146 29.167 59.182 29.504 60.219 C31.684 60.219 33.863 60.219 36.043 60.219 C34.279 61.5 32.516 62.781 30.753 64.062 C31.426 66.135 32.1 68.208 32.773 70.281 C31.01 69 29.247 67.719 27.483 66.438 C26.602 67.078 25.72 67.719 24.838 68.359 C23.957 69 23.075 69.641 22.193 70.281 C22.867 68.208 23.54 66.135 24.214 64.062 C22.451 62.781 20.687 61.5 18.924 60.219 C21.103 60.219 23.283 60.219 25.463 60.219 C26.136 58.146 26.81 56.073 27.483 54 Z" /> </path> <!-- 2) EQUILATERAL TRIANGLE (bottom-right) → CIRCLE → TRIANGLE --> <path id="tri" class="shape" mask="url(#smileMask)" d="M72.517 54 C73.166 55.125 73.816 56.25 74.465 57.375 C75.115 58.5 75.764 59.625 76.414 60.75 C77.063 61.875 77.713 63 78.362 64.125 C79.012 65.25 79.661 66.375 80.311 67.5 C79.012 67.5 77.713 67.5 76.414 67.5 C75.115 67.5 73.816 67.5 72.517 67.5 C71.218 67.5 69.919 67.5 68.62 67.5 C67.321 67.5 66.021 67.5 64.722 67.5 C65.372 66.375 66.021 65.25 66.671 64.125 C67.321 63 67.97 61.875 68.62 60.75 C69.269 59.625 69.919 58.5 70.568 57.375 C71.218 56.25 71.867 55.125 72.517 54 Z"> <animate attributeName="d" dur="11.2s" repeatCount="indefinite" calcMode="linear" keyTimes="0;0.4464286;0.5;0.9464286;1" values=" M72.517 54 C73.166 55.125 73.816 56.25 74.465 57.375 C75.115 58.5 75.764 59.625 76.414 60.75 C77.063 61.875 77.713 63 78.362 64.125 C79.012 65.25 79.661 66.375 80.311 67.5 C79.012 67.5 77.713 67.5 76.414 67.5 C75.115 67.5 73.816 67.5 72.517 67.5 C71.218 67.5 69.919 67.5 68.62 67.5 C67.321 67.5 66.021 67.5 64.722 67.5 C65.372 66.375 66.021 65.25 66.671 64.125 C67.321 63 67.97 61.875 68.62 60.75 C69.269 59.625 69.919 58.5 70.568 57.375 C71.218 56.25 71.867 55.125 72.517 54 Z; M72.517 54 C73.166 55.125 73.816 56.25 74.465 57.375 C75.115 58.5 75.764 59.625 76.414 60.75 C77.063 61.875 77.713 63 78.362 64.125 C79.012 65.25 79.661 66.375 80.311 67.5 C79.012 67.5 77.713 67.5 76.414 67.5 C75.115 67.5 73.816 67.5 72.517 67.5 C71.218 67.5 69.919 67.5 68.62 67.5 C67.321 67.5 66.021 67.5 64.722 67.5 C65.372 66.375 66.021 65.25 66.671 64.125 C67.321 63 67.97 61.875 68.62 60.75 C69.269 59.625 69.919 58.5 70.568 57.375 C71.218 56.25 71.867 55.125 72.517 54 Z; M50 20 C55.266 20 60.439 21.386 65 24.019 C69.561 26.652 73.348 30.439 75.981 35 C78.614 39.561 80 44.734 80 50 C80 55.266 78.614 60.439 75.981 65 C73.348 69.561 69.561 73.348 65 75.981 C60.439 78.614 55.266 80 50 80 C44.734 80 39.561 78.614 35 75.981 C30.439 73.348 26.652 69.561 24.019 65 C21.386 60.439 20 55.266 20 50 C20 44.734 21.386 39.561 24.019 35 C26.652 30.439 30.439 26.652 35 24.019 C39.561 21.386 44.734 20 50 20 Z; M50 20 C55.266 20 60.439 21.386 65 24.019 C69.561 26.652 73.348 30.439 75.981 35 C78.614 39.561 80 44.734 80 50 C80 55.266 78.614 60.439 75.981 65 C73.348 69.561 69.561 73.348 65 75.981 C60.439 78.614 55.266 80 50 80 C44.734 80 39.561 78.614 35 75.981 C30.439 73.348 26.652 69.561 24.019 65 C21.386 60.439 20 55.266 20 50 C20 44.734 21.386 39.561 24.019 35 C26.652 30.439 30.439 26.652 35 24.019 C39.561 21.386 44.734 20 50 20 Z; M72.517 54 C73.166 55.125 73.816 56.25 74.465 57.375 C75.115 58.5 75.764 59.625 76.414 60.75 C77.063 61.875 77.713 63 78.362 64.125 C79.012 65.25 79.661 66.375 80.311 67.5 C79.012 67.5 77.713 67.5 76.414 67.5 C75.115 67.5 73.816 67.5 72.517 67.5 C71.218 67.5 69.919 67.5 68.62 67.5 C67.321 67.5 66.021 67.5 64.722 67.5 C65.372 66.375 66.021 65.25 66.671 64.125 C67.321 63 67.97 61.875 68.62 60.75 C69.269 59.625 69.919 58.5 70.568 57.375 C71.218 56.25 71.867 55.125 72.517 54 Z" /> </path> <!-- 3) SQUARE (top-center) → CIRCLE → SQUARE --> <path id="square" class="shape" mask="url(#smileMask)" d="M43 17 C44.556 17 46.111 17 47.667 17 C49.222 17 50.778 17 52.333 17 C53.889 17 55.444 17 57 17 C57 18.556 57 20.111 57 21.667 C57 23.222 57 24.778 57 26.333 C57 27.889 57 29.444 57 31 C55.444 31 53.889 31 52.333 31 C50.778 31 49.222 31 47.667 31 C46.111 31 44.556 31 43 31 C43 29.444 43 27.889 43 26.333 C43 24.778 43 23.222 43 21.667 C43 20.111 43 18.556 43 17 Z"> <animate attributeName="d" dur="11.2s" repeatCount="indefinite" calcMode="linear" keyTimes="0;0.4464286;0.5;0.9464286;1" values=" M43 17 C44.556 17 46.111 17 47.667 17 C49.222 17 50.778 17 52.333 17 C53.889 17 55.444 17 57 17 C57 18.556 57 20.111 57 21.667 C57 23.222 57 24.778 57 26.333 C57 27.889 57 29.444 57 31 C55.444 31 53.889 31 52.333 31 C50.778 31 49.222 31 47.667 31 C46.111 31 44.556 31 43 31 C43 29.444 43 27.889 43 26.333 C43 24.778 43 23.222 43 21.667 C43 20.111 43 18.556 43 17 Z; M43 17 C44.556 17 46.111 17 47.667 17 C49.222 17 50.778 17 52.333 17 C53.889 17 55.444 17 57 17 C57 18.556 57 20.111 57 21.667 C57 23.222 57 24.778 57 26.333 C57 27.889 57 29.444 57 31 C55.444 31 53.889 31 52.333 31 C50.778 31 49.222 31 47.667 31 C46.111 31 44.556 31 43 31 C43 29.444 43 27.889 43 26.333 C43 24.778 43 23.222 43 21.667 C43 20.111 43 18.556 43 17 Z; M50 20 C55.266 20 60.439 21.386 65 24.019 C69.561 26.652 73.348 30.439 75.981 35 C78.614 39.561 80 44.734 80 50 C80 55.266 78.614 60.439 75.981 65 C73.348 69.561 69.561 73.348 65 75.981 C60.439 78.614 55.266 80 50 80 C44.734 80 39.561 78.614 35 75.981 C30.439 73.348 26.652 69.561 24.019 65 C21.386 60.439 20 55.266 20 50 C20 44.734 21.386 39.561 24.019 35 C26.652 30.439 30.439 26.652 35 24.019 C39.561 21.386 44.734 20 50 20 Z; M50 20 C55.266 20 60.439 21.386 65 24.019 C69.561 26.652 73.348 30.439 75.981 35 C78.614 39.561 80 44.734 80 50 C80 55.266 78.614 60.439 75.981 65 C73.348 69.561 69.561 73.348 65 75.981 C60.439 78.614 55.266 80 50 80 C44.734 80 39.561 78.614 35 75.981 C30.439 73.348 26.652 69.561 24.019 65 C21.386 60.439 20 55.266 20 50 C20 44.734 21.386 39.561 24.019 35 C26.652 30.439 30.439 26.652 35 24.019 C39.561 21.386 44.734 20 50 20 Z; M43 17 C44.556 17 46.111 17 47.667 17 C49.222 17 50.778 17 52.333 17 C53.889 17 55.444 17 57 17 C57 18.556 57 20.111 57 21.667 C57 23.222 57 24.778 57 26.333 C57 27.889 57 29.444 57 31 C55.444 31 53.889 31 52.333 31 C50.778 31 49.222 31 47.667 31 C46.111 31 44.556 31 43 31 C43 29.444 43 27.889 43 26.333 C43 24.778 43 23.222 43 21.667 C43 20.111 43 18.556 43 17 Z" /> </path> </g> </svg> </main> <!-- 3) SQUARE (top-center) → CIRCLE → SQUARE --></main>

 

🔸 CSS


:root { --orange: #ff8a00; }

html, body {
height: 100%;
margin: 0;
background: #111;
color: #ddd;
font-family: system-ui, -apple-system, Segoe UI, Roboto, sans-serif;
display: grid;
place-items: center;
}

/* Stage = 80% of the smaller viewport side as requested */
.stage {
width: 80vmin;
height: 80vmin;
display: grid;
place-items: center;
}

/* Single color from CSS; paths fill with currentColor */
.stage svg { color: var(—orange); }
.shape { fill: currentColor; }

/* Spin the entire orbit (outline + shapes) in perfect sync */
#orbit {
transform-origin: 50% 50%;
animation: spin 11.2s linear infinite;
}
@keyframes spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}

/* Smiley holes visible only during the circle phase — same phase split as morph */
@keyframes holesOnOff {
/* 0–44.64286%: hold original shapes — no holes */
0%, 44.64286% { opacity: 0; }
/* 50–94.64286%: hold the circle — show eyes/mouth */
50%, 94.64286% { opacity: 1; }
/* 94.64286–100%: morph back — hide again */
100% { opacity: 0; }
}
#smileHoles { animation: holesOnOff 11.2s linear infinite both; }

/* Respect reduced motion */
@media (prefers-reduced-motion: reduce) {
#orbit, #smileHoles { animation: none; }
}

Exapmle of Morph

]]>
https://t-jo.com/guide-building-4-synced-svg-shapes-that-morph-into-one-smiley-no-js.html/feed 0
☎️ Voice AI Assistant (phone) on AWS + Node.js + Nginx for Laravel / Symfony / WordPress / Drupal — easy to ship, harder to secure (security, real-time) https://t-jo.com/voice_ai_assistant_phone_aws_node_js_nginx_for_laravel_symfony_wordpress_drupal_easy_to_ship_harder_to_secure_security_real-time.html https://t-jo.com/voice_ai_assistant_phone_aws_node_js_nginx_for_laravel_symfony_wordpress_drupal_easy_to_ship_harder_to_secure_security_real-time.html#respond Sat, 11 Oct 2025 19:48:11 +0000 https://t-jo.com/?p=5790 A phone-based Voice AI Assistant built with Twilio/Asterisk + Node.js behind Nginx and running alongside your PHP apps (Laravel/Symfony/WordPress/Drupal) spins up fast. For SMB and mid-market, the security profile is risky: prompts/flows, call recordings and transcripts, and operational chains often live with third parties. That increases insider/leak exposure and creates vendor lock-in. Below is a safer direction and why PostgreSQL is outpacing MySQL in this context.

🧩 Architecture in 30 seconds
AWS (EC2/ALB) → Nginx (reverse-proxy) → Node.js (telephony + AI routing) + PHP backends (Laravel/Symfony/WordPress/Drupal). LLMs commonly via OpenAI / Azure OpenAI / Claude / AWS Bedrock. Primary DB: PostgreSQL (ideally PostGIS + pgvector); MySQL is fine for classic CRUD, but lags for geo/time windows and retrieval workloads.

⚠ Where the security risks show up
• Prompts and orchestration chains (your know-how) and verbose logging often end up outside your perimeter → higher risk of insider misuse and scenario cloning.
• Calls and PII flow through external STT/TTS/LLM providers; retention and deletion controls aren’t truly yours.
• Vendor lock-in: changing LLM/telephony later can break chains, adapters, and tooling.

🧭 AI is rewriting the playbook
The last 30 years of web favored content/blogging/e-commerce. With AI, geolocation + schedules for people/companies/resources take the front seat, and real-time orchestration becomes the default. That shift rewards PostgreSQL: PostGIS for coordinates/routes and pgvector for RAG/semantic retrieval. Even in PHP backends, this improves routing of calls/dispatch, time-window logic, and decision speed.

🏢 Why SMB and mid-market lean on on-prem
Many teams still trust local racks/Windows-style setups because the data path is short and auditable. When prompts/data stream to outside LLMs (OpenAI, Gemini, Grok, etc.), orgs inevitably consider a self-hosted / open-source route under Apache-2.0 and similar licenses—keeping prompts, vector indexes, and logs inside the perimeter. Configs and secrets live on the company’s own servers with clear RBAC and audit.

🛡 A safer path (sketch)
• Self-hosted LLM behind the firewall; RAG on PostgreSQL + pgvector.
• On-prem telephony: Asterisk/FreeSWITCH; record → transcribe → store locally (PostgreSQL).
• Nginx as a shield: TLS/mTLS, rate-limits/WAF, redact PII before anything leaves.
• Strict RBAC, minimal LLM logging, auditable access.
• Abstracted adapters for LLM/telephony from day one to avoid hard lock-in.

🚧 If you must stay cloud-first for now
• Keep prompts/chains and vector indexes inside your perimeter; send only minimal context outward.
• Redact PII in Node.js middleware and at Nginx.
• Plan your exit: interchangeable adapters for LLM/STT/TTS so a provider swap doesn’t break production.

🔎 What a phone Voice AI Assistant actually is (with real-time)
Think of it as an LLM core (ChatGPT variants like GPT-4o/4.1), an orchestrator (LangChain, LangGraph), speech-to-text (STT) engines (Whisper, Vosk), and text-to-speech (TTS) engines (Coqui TTS, Piper) working together in real-time to handle the call and drive actions across your systems.

📄 Appendix — drop in your Node.js skeleton (no secrets here)


import Fastify from 'fastify';
import WebSocket from 'ws';
import dotenv from 'dotenv';
import fastifyFormBody from '@fastify/formbody';
import fastifyWs from '@fastify/websocket';

// Load environment variables from .env file
dotenv.config({ path: '/var/www/twilio.solarneutrino.com/.env' });

// Retrieve the OpenAI API key from environment variables.
const { OPENAI_API_KEY } = process.env;

if (!OPENAI_API_KEY) {
console.error('Missing OpenAI API key. Please set it in the .env file.');
process.exit(1);
}

// Initialize Fastify
const fastify = Fastify();
fastify.register(fastifyFormBody);
fastify.register(fastifyWs);

// Constants
const SYSTEM_MESSAGE = 'Your name is Sophia. You are female. Your company, Solar Neutrino, provides marketing, SEO, and web development services. Answer any question and any language th
at you are asking. Try to make booking a schedule for your services. Use professional sellers scripts to get offers.';
const VOICE = 'sage'; //https://platform.openai.com/docs/guides/text-to-speech
const PORT = process.env.PORT || 5050; // Allow dynamic port assignment

// List of Event Types to log to the console. See the OpenAI Realtime API Documentation: https://platform.openai.com/docs/api-reference/realtime
const LOG_EVENT_TYPES = [
'error',
'response.content.done',
'rate_limits.updated',
'response.done',
'input_audio_buffer.committed',
'input_audio_buffer.speech_stopped',
'input_audio_buffer.speech_started',
'session.created'
];

// Show AI response elapsed timing calculations
const SHOW_TIMING_MATH = false;

// Root Route
fastify.get('/', async (request, reply) => {
reply.send({ message: 'Twilio Media Stream Server is running!' });
});

// Route for Twilio to handle incoming calls
// punctuation to improve text-to-speech translation
fastify.all('/incoming-call', async (request, reply) => {
const twimlResponse = `

Please wait while we connect your call to the A. I. voice assistant, powered by Solar Neutrino. O.K. you can start talking!



`;

reply.type('text/xml').send(twimlResponse);
});

// WebSocket route for media-stream
fastify.register(async (fastify) => {
fastify.get('/media-stream', { websocket: true }, (connection, req) => {
console.log('Client connected');

// Connection-specific state
let streamSid = null;
let latestMediaTimestamp = 0;
let lastAssistantItem = null;
let markQueue = [];
let responseStartTimestampTwilio = null;

const openAiWs = new WebSocket('wss://api.openai.com/v1/realtime?model=gpt-4o-realtime-preview-2024-10-01', {
headers: {
Authorization: `Bearer ${OPENAI_API_KEY}`,
"OpenAI-Beta": "realtime=v1"
}
});

// Control initial session with OpenAI
const initializeSession = () => {
const sessionUpdate = {
type: 'session.update',
session: {
turn_detection: { type: 'server_vad' },
input_audio_format: 'g711_ulaw',
output_audio_format: 'g711_ulaw',
voice: VOICE,
instructions: SYSTEM_MESSAGE,
modalities: ["text", "audio"],
temperature: 0.8,
}
};

console.log('Sending session update:', JSON.stringify(sessionUpdate));
openAiWs.send(JSON.stringify(sessionUpdate));

// Uncomment the following line to have AI speak first:
// sendInitialConversationItem();
};

// Send initial conversation item if AI talks first
const sendInitialConversationItem = () => {
const initialConversationItem = {
type: 'conversation.item.create',
item: {
type: 'message',
role: 'user',
content: [
{
type: 'input_text',
text: 'Greet the user with "Hello there! I am an AI voice assistant powered by Twilio and the OpenAI Realtime API. You can
ask me for facts, jokes, or anything you can imagine. How can I help you?"'
}
] }
};

if (SHOW_TIMING_MATH) console.log('Sending initial conversation item:', JSON.stringify(initialConversationItem));
openAiWs.send(JSON.stringify(initialConversationItem));
openAiWs.send(JSON.stringify({ type: 'response.create' }));
};

// Handle interruption when the caller's speech starts
const handleSpeechStartedEvent = () => {
if (markQueue.length > 0 && responseStartTimestampTwilio != null) {
const elapsedTime = latestMediaTimestamp - responseStartTimestampTwilio;
if (SHOW_TIMING_MATH) console.log(`Calculating elapsed time for truncation: ${latestMediaTimestamp} - ${responseStartTimestampTwilio}
= ${elapsedTime}ms`);

if (lastAssistantItem) {
const truncateEvent = {
type: 'conversation.item.truncate',
item_id: lastAssistantItem,
content_index: 0,
audio_end_ms: elapsedTime
};
if (SHOW_TIMING_MATH) console.log('Sending truncation event:', JSON.stringify(truncateEvent));
openAiWs.send(JSON.stringify(truncateEvent));
}

connection.send(JSON.stringify({
event: 'clear',
streamSid: streamSid
}));

// Reset
markQueue = [];
lastAssistantItem = null;
responseStartTimestampTwilio = null;
}
};

// Send mark messages to Media Streams so we know if and when AI response playback is finished
const sendMark = (connection, streamSid) => {
if (streamSid) {
const markEvent = {
event: 'mark',
streamSid: streamSid,
mark: { name: 'responsePart' }
};
connection.send(JSON.stringify(markEvent));
markQueue.push('responsePart');
}
};

// Open event for OpenAI WebSocket
openAiWs.on('open', () => {
console.log('Connected to the OpenAI Realtime API');
setTimeout(initializeSession, 100);
});

// Listen for messages from the OpenAI WebSocket (and send to Twilio if necessary)
openAiWs.on('message', (data) => {
try {
const response = JSON.parse(data);

if (LOG_EVENT_TYPES.includes(response.type)) {
console.log(`Received event: ${response.type}`, response);
}

if (response.type === 'response.audio.delta' && response.delta) {
const audioDelta = {
event: 'media',
streamSid: streamSid,
media: { payload: response.delta }
};
connection.send(JSON.stringify(audioDelta));

// First delta from a new response starts the elapsed time counter
if (!responseStartTimestampTwilio) {
responseStartTimestampTwilio = latestMediaTimestamp;
if (SHOW_TIMING_MATH) console.log(`Setting start timestamp for new response: ${responseStartTimestampTwilio}ms`);
}

if (response.item_id) {
lastAssistantItem = response.item_id;
}

sendMark(connection, streamSid);
}

if (response.type === 'input_audio_buffer.speech_started') {
handleSpeechStartedEvent();
}
} catch (error) {
console.error('Error processing OpenAI message:', error, 'Raw message:', data);
}
});

// Handle incoming messages from Twilio
connection.on('message', (message) => {
try {
const data = JSON.parse(message);

switch (data.event) {
case 'media':
latestMediaTimestamp = data.media.timestamp;
if (SHOW_TIMING_MATH) console.log(`Received media message with timestamp: ${latestMediaTimestamp}ms`);
if (openAiWs.readyState === WebSocket.OPEN) {
const audioAppend = {
type: 'input_audio_buffer.append',
audio: data.media.payload
};
openAiWs.send(JSON.stringify(audioAppend));
}
break;
case 'start':
streamSid = data.start.streamSid;
console.log('Incoming stream has started', streamSid);

// Reset start and media timestamp on a new stream
responseStartTimestampTwilio = null;
latestMediaTimestamp = 0;
break;
case 'mark':
if (markQueue.length > 0) {
markQueue.shift();
}
break;
default:
console.log('Received non-media event:', data.event);
break;
}
} catch (error) {
console.error('Error parsing message:', error, 'Message:', message);
}
});

// Handle connection close
connection.on('close', () => {
if (openAiWs.readyState === WebSocket.OPEN) openAiWs.close();
console.log('Client disconnected.');
});

// Handle WebSocket close and errors
openAiWs.on('close', () => {
console.log('Disconnected from the OpenAI Realtime API');
});

openAiWs.on('error', (error) => {
console.error('Error in the OpenAI WebSocket:', error);
});
});
});

fastify.listen({ port: PORT }, (err) => {
if (err) {
console.error(err);
process.exit(1);
}
console.log(`Server is listening on port ${PORT}`);
});

Tags: security, Voice AI, phone AI assistant, real-time, Twilio, Asterisk, Node.js, Nginx, Laravel, Symfony, WordPress, Drupal, OpenAI API, Azure OpenAI, Anthropic Claude, AWS Bedrock, RAG, LangChain, LangGraph, PostgreSQL, PostGIS, pgvector, STT (Whisper, Vosk), TTS (Coqui TTS, Piper), call recording, transcription, on-prem, self-hosted LLM

LinkedIn Post Voice AI Assistant (Phone)

]]>
https://t-jo.com/voice_ai_assistant_phone_aws_node_js_nginx_for_laravel_symfony_wordpress_drupal_easy_to_ship_harder_to_secure_security_real-time.html/feed 0
Fix Shared SMB Mounts on macOS Tahoe After Updates: Auto-Mount on Login Without Glitches https://t-jo.com/fix-shared-smb-mounts-on-macos-tahoe-after-updates-auto-mount-on-login-without-glitches.html https://t-jo.com/fix-shared-smb-mounts-on-macos-tahoe-after-updates-auto-mount-on-login-without-glitches.html#comments Sun, 05 Oct 2025 21:09:03 +0000 https://t-jo.com/?p=5782 pre, code {font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace;} pre {background:#0f111a; color:#e6e6e6; padding:1rem; border-radius:8px; overflow:auto;} kbd {background:#686868; border:1px solid #ddd; border-bottom-width:2px; padding:0 .35em; border-radius:4px;} .note {background:#484848; border-left:4px solid #3a8; padding:.75rem 1rem;} .warning {background:#555555; border-left:4px solid #f90; padding:.75rem 1rem;}

Fix Shared SMB Mounts on macOS Tahoe After Updates: Auto-Mount on Login Without Glitches

After a macOS Tahoe update, many admins discover their SMB shares no longer auto-mount at login, or they mount to unstable paths like /Volumes/share-1 and disappear between reboots. Finder items in “Login Items” are fragile, and scripting only mount_smbfs ignores your Keychain (and often prompts at the worst time).

This post gives you a battle-tested fix: a tiny Python script that:

  • Uses your existing Keychain credentials (no passwords in files).
  • Waits for the right network, confirms the server by ARP MAC, then mounts.
  • Handles Finder’s dynamic mount points by creating stable symlinks under ~/Network.
  • Runs as a user LaunchAgent, so it’s reliable across reboots and sleeps.
What we’ll build
A LaunchAgent that runs a Python script every 30 seconds (and on login). The script checks you’re on the expected subnet, confirms the target SMB server’s MAC via ARP, then mounts shares via Finder (so Keychain supplies the password). If Finder is stubborn, it falls back to mount_smbfs at a user-writable mount point. Finally, it drops deterministic symlinks under ~/Network so your paths don’t change.

1) One-time prep: save the credential in Keychain

  1. Open Finder → ⌘K (Go → Connect to Server…).
  2. Connect to smb://192.168.0.1/share (and any other share you need, e.g. scan).
  3. Sign in as user user, check “Remember this password in my keychain”. Disconnect after it mounts.

Heads-up: The script never stores your password. It relies entirely on the saved Keychain item for smb://192.168.0.1.

2) Install the script

Create a small workspace and drop in mount_smb.py.

# create the folders
mkdir -p /Users/user/bin /Users/user/Network

# open an editor and paste the script below
nano /Users/user/bin/mount_smb.py

# make it executable
chmod +x /Users/user/bin/mount_smb.py

mount_smb.py

Python 2/3 compatible, standard library only.

#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import print_function

import os, sys, time, subprocess, shlex, re, errno, datetime, socket, struct

# ------------------ CONFIG ------------------
CONFIG = {
    # SMB server and shares
    'SERVER_IP'   : '192.168.0.1',
    'SERVER_MAC'  : '00:11:22:33:44:55',   # any case / separators accepted
    'USERNAME'    : 'user',
    'SHARES'      : ['share', 'scan'],

    # Only act when the primary IPv4 is in this subnet
    'WIFI_SUBNET' : '192.168.0.0/24',

    # Paths
    'NETWORK_DIR' : os.path.expanduser('~/Network'),
    'LOG_PATH'    : os.path.expanduser('~/Library/Logs/mount_smb_python.log'),

    # Timing
    'ARP_RETRIES' : 10,   # ARP retries (1s apart)
    'MOUNT_TRIES' : 60,   # osascript attempts (1s apart)
}
# --------------------------------------------

def now(): return datetime.datetime.now().strftime("[%Y-%m-%d %H:%M:%S]")

def log(msg):
    line = u"{} {}".format(now(), msg)
    try:
        sys.stdout.write(line + "\n"); sys.stdout.flush()
    except: pass
    try:
        with open(CONFIG['LOG_PATH'], 'a') as f: f.write(line + "\n")
    except: pass

def run(cmd, shell=False):
    try:
        args = cmd if (shell or not isinstance(cmd, str)) else shlex.split(cmd)
        p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=shell)
        out, err = p.communicate(); rc = p.returncode
        if not isinstance(out, str): out = out.decode('utf-8', 'ignore')
        if not isinstance(err, str): err = err.decode('utf-8', 'ignore')
        return rc, out, err
    except OSError as e:
        return 127, "", str(e)

def ip_to_int(ip): return struct.unpack("!I", socket.inet_aton(ip))[0]
def cidr_match(ip, cidr):
    try:
        net, pfx = cidr.split('/'); pfx = int(pfx)
        mask = (0xffffffff << (32 - pfx)) & 0xffffffff
        return (ip_to_int(ip) & mask) == (ip_to_int(net) & mask)
    except: return False

def default_interface():
    rc, out, _ = run(['/sbin/route', '-n', 'get', 'default'])
    if rc != 0: return None
    m = re.search(r'interface:\s+([^\s]+)', out)
    return m.group(1) if m else None

def ipv4_for_iface(iface):
    if not iface: return None
    rc, out, _ = run(['/usr/sbin/ipconfig', 'getifaddr', iface])
    ip = out.strip()
    return ip if rc == 0 and re.match(r'^\d+\.\d+\.\d+\.\d+$', ip) else None

def normalize_mac(s):
    if not s: return ""
    s = re.sub(r'[^0-9a-f]', '', s.strip().lower())
    return ':'.join( for i in range(0, len(s), 2)])

def arp_mac_for_ip(ip):
    rc, out, _ = run(['/usr/sbin/arp', '-n', ip])
    if rc != 0: return ""
    m = re.search(r'\bat\s+([0-9a-fA-F:\- ]+)\s+on\s', out)
    return normalize_mac(m.group(1)) if m else ""

def ensure_dir(path):
    try: os.makedirs(path)
    except OSError as e:
        if e.errno != errno.EEXIST: raise

def mounted_path_for_share(server_ip, share_name):
    rc, out, _ = run(['/sbin/mount'])
    if rc != 0: return None
    pattern = re.compile(r'@{}/{}\s+on\s+(\S+)'.format(re.escape(server_ip), re.escape(share_name)))
    for line in out.splitlines():
        if '@{}/{}'.format(server_ip, share_name) in line:
            m = pattern.search(line)
            if m: return m.group(1)
    return None

def mount_via_osascript(server_ip, share_name, username):
    script = u'''try
mount volume "smb://{}/{}" as user name "{}"
end try'''.format(server_ip, share_name, username)
    rc, _, _ = run(['/usr/bin/osascript', '-e', script])
    return rc == 0

def mount_via_mount_smbfs(server_ip, share_name, username, dest):
    ensure_dir(dest)
    rc, _, err = run(['/sbin/mount_smbfs', '-f', '0644', '-d', '0755',
                      '//{}@{}/{}'.format(username, server_ip, share_name), dest])
    if rc != 0: log("mount_smbfs error for {}: rc={} err={}".format(share_name, rc, err.strip()))
    return rc == 0

def stable_link(link_path, target_path):
    try:
        if os.path.islink(link_path) or os.path.exists(link_path):
            cur = os.path.realpath(link_path)
            if cur == os.path.realpath(target_path): return
            try: os.remove(link_path)
            except: pass
        os.symlink(target_path, link_path)
    except Exception as e:
        log("symlink error: {} -> {} ({})".format(link_path, target_path, e))

def main():
    cfg = CONFIG
    ensure_dir(os.path.dirname(cfg['LOG_PATH']))
    ensure_dir(cfg['NETWORK_DIR']); ensure_dir(os.path.join(cfg['NETWORK_DIR'], '.mnt'))

    iface = default_interface()
    ip = ipv4_for_iface(iface)
    if not ip:
        log("No IPv4 yet (iface={}), exiting.".format(iface)); return 0
    if not cidr_match(ip, cfg['WIFI_SUBNET']):
        log("IPv4 {} not in {}, exiting.".format(ip, cfg['WIFI_SUBNET'])); return 0
    log("Default iface={} IPv4={} matches {} — proceeding.".format(iface, ip, cfg['WIFI_SUBNET']))

    want = normalize_mac(cfg['SERVER_MAC']); mac = ""
    for _ in range(cfg['ARP_RETRIES']):
        mac = arp_mac_for_ip(cfg['SERVER_IP'])
        if mac: break
        time.sleep(1)
    if want and mac and mac != want:
        log("ARP MAC mismatch for {}: saw {}, expected {} — exiting.".format(cfg['SERVER_IP'], mac, want)); return 0
    log("ARP {}: {}".format(cfg['SERVER_IP'], mac or "unresolved"))

    for share in cfg['SHARES']:
        mp = mounted_path_for_share(cfg['SERVER_IP'], share)
        if mp:
            log("{} already mounted at {}".format(share, mp))
            stable_link(os.path.join(cfg['NETWORK_DIR'], share), mp)
            continue

        log("Trying to mount {} via Finder (osascript)...".format(share))
        ok = False
        for attempt in range(cfg['MOUNT_TRIES']):
            mount_via_osascript(cfg['SERVER_IP'], share, cfg['USERNAME'])
            time.sleep(1)
            mp = mounted_path_for_share(cfg['SERVER_IP'], share)
            if mp: ok = True; break
            if (attempt + 1) % 10 == 0: log("Retry {} for {}...".format(attempt + 1, share))

        if not ok:
            log("Finder path did not attach; falling back to mount_smbfs for {}.".format(share))
            dest = os.path.join(cfg['NETWORK_DIR'], '.mnt', share)
            ok = mount_via_mount_smbfs(cfg['SERVER_IP'], share, cfg['USERNAME'], dest)
            mp = dest if ok else None

        if ok and mp:
            log("Mounted {} at {}".format(share, mp))
            stable_link(os.path.join(cfg['NETWORK_DIR'], share), mp)
        else:
            log("Failed to mount {}".format(share))
    return 0

if __name__ == '__main__':
    try: sys.exit(main())
    except KeyboardInterrupt: pass

3) LaunchAgent: run it at login and when the network changes

Create a user LaunchAgent:

cat > /Users/user/Library/LaunchAgents/com.user.mountsmb.py.plist <<'EOF'



  Labelcom.user.mountsmb.py
  ProgramArguments
    
    /opt/homebrew/bin/python3
    /Users/user/bin/mount_smb.py
  
  RunAtLoad
  KeepAliveNetworkState
  StartInterval30
  LimitLoadToSessionTypeAqua
  StandardOutPath/Users/user/Library/Logs/mount_smb_python.log
  StandardErrorPath/Users/user/Library/Logs/mount_smb_python.log

EOF

chmod 644 /Users/user/Library/LaunchAgents/com.user.mountsmb.py.plist

# (Optional) Intel Homebrew path:
# /usr/local/bin/python3 /Users/user/bin/mount_smb.py

Load or reload the agent:

launchctl bootout gui/$(id -u)/com.user.mountsmb.py 2>/dev/null || true
launchctl bootstrap gui/$(id -u) /Users/user/Library/LaunchAgents/com.user.mountsmb.py.plist
launchctl kickstart -k gui/$(id -u)/com.user.mountsmb.py

4) Verify

# check logs
tail -n 80 /Users/user/Library/Logs/mount_smb_python.log

# list stable paths regardless of Finder's dynamic mount points
ls -la  /Users/user/Network
ls -la  /Users/user/Network/share
ls -la  /Users/user/Network/scan

Troubleshooting

  • “env: python: No such file or directory” — Point ProgramArguments at the full python3 path (e.g. /opt/homebrew/bin/python3 on Apple Silicon).
  • Keychain prompts — Reconnect once in Finder to smb://192.168.0.1/share, check “Remember in Keychain”.
  • Wrong network — The script exits if your primary IPv4 isn’t in 192.168.0.0/24. Adjust WIFI_SUBNET in the config.
  • ARP mismatch — If your NAS has multiple NICs or failover, update SERVER_MAC.

Uninstall

launchctl bootout gui/$(id -u)/com.user.mountsmb.py 2>/dev/null || true
rm -f /Users/user/Library/LaunchAgents/com.user.mountsmb.py.plist
rm -f /Users/user/bin/mount_smb.py
rm -f /Users/user/Library/Logs/mount_smb_python.log

Result: your SMB shares mount cleanly after every reboot or network flap, paths stay stable via ~/Network/<share>, and you never hard-code a secret.

]]>
https://t-jo.com/fix-shared-smb-mounts-on-macos-tahoe-after-updates-auto-mount-on-login-without-glitches.html/feed 1
Symfony VS Laravel https://t-jo.com/symfony-vs-laravel.html https://t-jo.com/symfony-vs-laravel.html#respond Sun, 02 Jun 2024 06:06:16 +0000 https://t-jo.com/?p=5762

Laravel vs Symfony Comparison

Framework Features Comparison

Laravel 11 Symfony 7 Description
Artisan Symfony Console Command-line tools for managing the application, running migrations, generating code, etc.
Tinker Symfony REPL (PsySH) Interactive shell for working with the application from the command line.
Eloquent Doctrine ORM ORM (Object-Relational Mapping) for database interaction.
Facade DB Doctrine DBAL Interface for low-level database operations, below the ORM level.
Laravel Mix (uses npx) Webpack Encore Tools for compiling and managing frontend resources such as CSS and JavaScript.
Blade Twig Templating engines for generating HTML on the server side.
Laravel Scout Elasticsearch (via package) Tools for full-text search, integrating with Elasticsearch and other search systems.
Laravel Socialite HWIOAuthBundle Packages for integrating OAuth social authorizations.
Laravel Passport LexikJWTAuthenticationBundle API authentication implementations using tokens.
Laravel Horizon Symfony Messenger Tools for managing job queues and background processes.

Additional Features Comparison

Laravel 11 Symfony 7 Description
Paginate in Eloquent Pagerfanta or KnpPaginator Pagination of data, implemented in ORM or through third-party bundles.
Laravel Fortify (supports 2FA) SchebTwoFactorBundle Solutions for two-factor authentication (2FA) in web applications.
Laravel Sanctum LexikJWTAuthenticationBundle Solutions for API and SPA authentication, providing tokens and handling session states.

Directory Structure Comparison: Laravel vs Symfony

Laravel 11 Symfony 7 Description
/app /src Main application code, including controllers, models, and other classes.
/app/Http /src/Controller Controllers that manage the logic of incoming requests.
/app/Models /src/Entity Models in Laravel, entities in Symfony (used with Doctrine ORM).
/resources/views /templates User interface templates. Laravel uses Blade, Symfony uses Twig.
/resources/js /assets/js JavaScript files. Symfony uses Webpack Encore for managing assets.
/resources/sass /assets/css SASS/SCSS styles for frontend.
/bootstrap /config/bootstrap.php Bootstrap scripts in Laravel. Part of configuration in Symfony.
/config /config Application configuration files.
/database/migrations /migrations Database migrations.
/public /public Public root directory, the web application entry point.
/routes /config/routes Application routing definitions.
/storage /var Storage for log files, cache, and sessions.
/tests /tests Application tests.
/vendor /vendor Third-party libraries and dependencies installed via Composer.
.env .env Environment configuration file managing sensitive data and configurations.
composer.json composer.json Defines PHP dependencies and other Composer parameters.
package.json package.json Defines Node.js dependencies for frontend tools and libraries.
Laravel Command Symfony Command Description
php artisan list php bin/console list Displays all registered commands in the application.
php artisan make:model ModelName php bin/console make:entity EntityName Creates a new model in Laravel and a new entity in Symfony.
php artisan make:controller ControllerName php bin/console make:controller ControllerName Generates a new controller class in both frameworks.
php artisan make:event EventName php bin/console make:event EventName Creates a new event class in both Laravel and Symfony.
php artisan make:listener ListenerName —event=EventName php bin/console make:subscriber EventName Generates an event listener in Laravel and an event subscriber in Symfony.
php artisan migrate php bin/console doctrine:migrations:migrate Executes database migrations in both Laravel and Symfony.
php artisan serve php bin/console server:run Starts a development server for Laravel and Symfony applications.
php artisan route:list php bin/console debug:router Displays routes registered in the application for both frameworks.
php artisan cache:clear php bin/console cache:clear Clears the application cache in both Laravel and Symfony.
php artisan config:cache php bin/console cache:warmup Creates a cache file for faster configuration loading in Laravel and warms up the cache in Symfony.
php artisan queue:work php bin/console messenger:consume Starts the queue worker in Laravel and consumes messages from the message queue in Symfony.
php artisan make:middleware MiddlewareName php bin/console make:middleware MiddlewareName Generates a new middleware class in both Laravel and Symfony.
php artisan make:migration MigrationName php bin/console make:migration Creates a new database migration file in both frameworks.
php artisan db:seed php bin/console doctrine:fixtures:load Seeds the database with records in Laravel and loads data fixtures in Symfony.
php artisan tinker php bin/console psysh Provides an interactive shell for Laravel and Symfony, powered by PsySH.
php artisan optimize php bin/console cache:warmup Optimizes the framework loading in Laravel and preloads cache in Symfony.
php artisan schedule:run php bin/console scheduler:execute Runs the scheduled tasks configured in Laravel and Symfony.
]]>
https://t-jo.com/symfony-vs-laravel.html/feed 0
MySQL vs PostgreSQL: Top Features https://t-jo.com/mysql_vs_postgresql_top_features.html https://t-jo.com/mysql_vs_postgresql_top_features.html#respond Tue, 23 Apr 2024 22:15:36 +0000 https://t-jo.com/?p=5710 Let’s dive into the top 5 perks of using MySQL and PostgreSQL

Throwing in some cool stuff about locations and JSON. Plus, I’ll spotlight something unique to MySQL that PostgreSQL doesn’t offer. Let’s keep it chill and straightforward, like chatting on a sunny beach in Cali.

MySQL: The Go-To for Getting Things Rolling

  • Super User-Friendly: MySQL is like your friendly neighborhood guide. It’s super easy to set up and manage, which means you won’t spend hours figuring things out.
  • Costs? Nope, It’s free, man! Open-source helps keep those budget blues away.
  • Solid Rep for Large Traffic Handling: Scaling up? MySQL can handle the pressure like a pro surfer riding those big waves.
  • Widespread Love: With a massive community of users, finding help or resources is as easy as finding a taco truck in SoCal.
  • The Unique MySQL Replication: MySQL offers replication features that allow your data to be duplicated across multiple servers easily. PostgreSQL has replication too, but MySQL’s approach is super streamlined and has been in the game longer.

PostgreSQL: The Brainy Contender for Complex Needs

  • Data Integrity is King: If your project needs bulletproof accuracy, PostgreSQL is your go-to. It’s all about keeping your data clean and precise.
  • Advanced Data Types and Functions: What about JSON and locations? PostgreSQL effortlessly handles complex data like JSON and geospatial data, perfect for when you need more than just the basics.
  • Extensibility for Days: It’s like the custom surfboard of databases. You can tweak, extend, and make it exactly what you need.
  • PostGIS Extension: For those who need to manage and analyze location data, PostGIS makes PostgreSQL a powerhouse for geographic information systems (GIS).
  • Thriving Community and Open-Source: PostgreSQL is backed by a vibrant community, and being open-source means it’s continually evolving.

JSON and Location Handling: The Cool Extras

Both MySQL and PostgreSQL handle JSON, letting you store and query JSON data directly. This is handy for modern web apps where JSON is the leading data exchange language. Regarding location data, PostgreSQL, with its PostGIS extension, is like having GPS on steroids. It lets you do all sorts of spatial queries and analyses, which is incredible for any map-heavy app.

So, whether you’re building a startup from your garage or setting up a complex scientific database, choosing between MySQL and PostgreSQL depends on your needs. MySQL’s ease and efficiency make it perfect for business applications, while PostgreSQL’s robustness suits detailed, complex projects that need to handle intricate data with precision. Just pick the vibe that fits your project’s style!

Roman Primerov

]]>
https://t-jo.com/mysql_vs_postgresql_top_features.html/feed 0
Docker Commands Cheat Sheet for Backend Development https://t-jo.com/docker-commands-cheat-sheet-for-backend-development.html https://t-jo.com/docker-commands-cheat-sheet-for-backend-development.html#respond Fri, 19 Apr 2024 21:15:05 +0000 https://t-jo.com/?p=5701 Docker Commands Cheat Sheet

This cheat sheet is a reminder for myself, which might come in handy in the daily life of a PHP and Node.js backend developer.

Container Management

  • List running containers: docker ps
  • List all containers (including stopped): docker ps -a
  • Create a container from an image without starting it: docker create
  • Rename a container: docker rename [container] [new-name]
  • Remove a container: docker rm [container], use -f to force remove a running container.
  • View logs for a container: docker logs [container], use -f --until=[interval] to display logs up to a certain point in time.
  • Update a container’s configuration: docker update [container]
  • Show container’s port mappings: docker port [container]
  • Show running processes in a container: docker top [container]
  • Show live resource usage statistics of a container: docker stats [container]
  • Show changes to files or directories on a container’s filesystem: docker diff [container]
  • Copy files to/from a container: docker cp [file-path] CONTAINER:[path]

Networking

  • View available networks: docker network ls
  • Remove a network: docker network rm [network]
  • Connect a container to a network: docker network connect [network] [container]
  • Disconnect a container from a network: docker network disconnect [network] [container]
  • Show detailed information about a network: docker network inspect [network]

Image Management

  • Create an image from a Dockerfile: docker build [dockerfile-path], use -t [name]:[tag] to tag it.
  • Pull an image from a registry: docker pull
  • Push an image to a registry: docker push
  • Save changes in a container as a new image: docker commit [container] [new-image]
  • View all locally stored top-level images: docker images
  • Remove an image: docker rmi
  • Show history of an image: docker history
  • Save an image to a tar archive file: docker save > [tar-file]
  • Remove unused images: docker image prune

The docker commands cheat sheet: #CheatSheetDocker

]]>
https://t-jo.com/docker-commands-cheat-sheet-for-backend-development.html/feed 0
Managing Roles and Permissions in Laravel 11 with Spatie Permission https://t-jo.com/managing_roles_and_permissions_in_laravel_11_with_spatie_permission.html https://t-jo.com/managing_roles_and_permissions_in_laravel_11_with_spatie_permission.html#respond Fri, 15 Mar 2024 05:47:34 +0000 https://t-jo.com/?p=5743 This guide provides a straightforward approach to setting up and managing roles and permissions in Laravel 11 using the Spatie Permission package.

Step 1: Install Spatie Laravel Permission

Start by installing the package via Composer and publishing its configurations:

composer require spatie/laravel-permission
php artisan vendor:publish --provider="Spatie\Permission\PermissionServiceProvider"
php artisan migrate

Step 2: Rollback the Last Migration

If needed, you can rollback the last migration with this command:

php artisan migrate:rollback --step=1

Step 3: Re-run Migrations

If tables were deleted or require recreation, run:

php artisan migrate

Step 4: Update the User Model

Ensure the User model uses the HasRoles trait from Spatie:

<?php

namespace App\Models;

use Illuminate\Foundation\Auth\User as Authenticatable;
use Spatie\Permission\Traits\HasRoles;

class User extends Authenticatable
{
    use HasRoles;

    // Other model methods...
}

Step 5: Create and Run a Seeder for Roles and Permissions

Create a seeder then populate roles and permissions:

php artisan make:seeder RolesAndPermissionsSeeder
php artisan db:seed --class=RolesAndPermissionsSeeder

Here’s what your seeder might look like:

<?php

namespace Database\Seeders;

use Illuminate\Database\Seeder;
use App\Models\User;
use Spatie\Permission\Models\Role;
use Spatie\Permission\Models\Permission;

class RolesAndPermissionsSeeder extends Seeder
{
    public function run()
    {
        // Create roles
        $roleSuperAdmin = Role::create(['name' => 'superadmin']);
        $roleAdmin = Role::create(['name' => 'admin']);
        $roleUser = Role::create(['name' => 'user']);
        // More roles setup...

        // Assign roles to users
        $userOne = User::where('email', 'j@solarneutrino.com')->first();
        if ($userOne) {
            $userOne->assignRole('superadmin');
        }

        $userTwo = User::where('email', 'user@gmail.com')->first();
        if ($userTwo) {
            $userTwo->assignRole('user');
        }
    }
}

Follow these steps to effectively manage roles and permissions in your Laravel 11 application using the Spatie Permission package.

Step 6: Auto-Assign ‘User’ Role on Registration

To automatically assign the «user» role to all newly registered users, you need to modify the registration handling method. This is usually the create method in the RegisterController.

  1. Open the RegisterController.php file, typically located in the app/Http/Controllers/Auth directory.

  2. Add the role assignment in the create method after creating a new user:

    protected function create(array $data)
    {
        $user = User::create([
            'name' => $data['name'],
            'email' => $data['email'],
            'password' => Hash::make($data['password']),
        ]);
    
        // Assigning the "user" role
        $user->assignRole('user');
    
        return $user;
    }
    

This code snippet ensures that every new user is automatically granted the «user» role upon registration.

Step 7: Using @role and @hasrole Directives in Blade Templates

To manage content visibility based on user roles within Blade templates, Laravel’s Spatie Permission package provides two useful directives: @role and @hasrole. These directives help in rendering content conditionally based on the user’s roles.

Examples:

<!-- Display content only if the user has the "superadmin" role. Checks for any of the specified roles if multiple are provided. -->
@role('superadmin')
    <p>This text can see user from Superadmin group</p>
@endrole

<!-- Alternative way using @hasrole, strictly checks for the specified role. Used for checking a single role. -->
@hasrole('superadmin')
    <p>This text can see user from Superadmin group</p>
@endhasrole

<!-- Display content only if the user has the "user" role. Checks for any of the specified roles if multiple are provided. -->
@role('user')
    <p>This text can see user from User group</p>
@endrole

<!-- Alternative way using @hasrole, strictly checks for the specified role. Used for checking a single role. -->
@hasrole('user')
    <p>This text can see user from User group</p>
@endhasrole

Explanations:

  • @role: This directive checks if the current authenticated user has one or more specified roles. If multiple roles are passed (separated by commas), it returns true if the user has at least one of them. In the provided examples, only one role is specified, so the behavior is similar to @hasrole.
  • @hasrole: This directive strictly checks if the user has the specified role. It’s ideal for situations where you need to verify the presence of only one specific role. This directive always takes only one role at a time.

These directives are instrumental in controlling what content a user can see based on their roles, enhancing security and user experience by tailoring the UI to meet individual user permissions.

Step 8: Applying Roles to Routes or Route Groups

In Laravel 11, the structure for handling middleware has changed significantly, including the deprecation of the app/Http/Kernel.php file. Middleware can now be applied directly in the routing files such as web.php or api.php. Here’s how to set up and use Spatie Permission Middleware to restrict route access to users with specific roles.

Registering and Applying Middleware

You can register your middleware directly in the routing file. Below is a step-by-step guide on how to apply the Spatie Permission Middleware directly to your routes:

  1. Add the Middleware Namespace:

    At the beginning of your routing file (e.g., routes/web.php), add the namespace for the middleware:

    <?php
    use Spatie\Permission\Middlewares\RoleMiddleware;
    ?>
    
  2. Apply Middleware to a Route:

    Apply the middleware directly to the routes as follows:

    <?php
    use Illuminate\Support\Facades\Route;
    
    Route::middleware(['auth', 'verified', RoleMiddleware::class . ':superadmin'])
        ->get('/contact-messages', function () {
            return view('contact-messages');
        })->name('contact-messages');
    ?>
    

    Note that we pass the RoleMiddleware class with the role parameter (superadmin) directly into the middleware array.

Laravel 10, 9, …

To secure routes based on user roles, Laravel allows the application of middleware to individual routes or groups of routes. Using Spatie’s permission package, you can enforce role-based access controls effectively.

Applying Roles to a Single Route:

use Illuminate\Support\Facades\Route;

// Ensure you have registered the 'role' middleware in your old version Laravel - Kernel.php after Laravel 11 it can be seeder class or another tinker way. 
Route::get('/admin', function () {
    return view('admin.dashboard');
})->middleware('role:admin');

This route is accessible only to users who have the ‘admin’ role. If a user without this role tries to access it, they will be redirected as defined by your middleware handling.

Applying Roles to a Route Group:

use Illuminate\Support\Facades\Route;

Route::middleware(['role:admin'])->group(function () {
    Route::get('/admin/dashboard', function() {
        return view('admin.dashboard');
    });
    Route::get('/admin/settings', function() {
        return view('admin.settings');
    });
});

This group of routes is secured with the ‘admin’ role, ensuring that all routes within this group require a user to be an ‘admin’ to gain access. This method is particularly effective for sectioning parts of your application that should be restricted to users with specific roles.

Note: It’s important to ensure that your Kernel.php file is properly configured to recognize the Spatie middleware. Add the Spatie middleware to your $routeMiddleware array if it’s not already present:

protected $routeMiddleware = [
    // other middleware
    'role' => \Spatie\Permission\Middlewares\RoleMiddleware::class,
];

Following these best practices not only enhances the security of your application by ensuring proper role-based access control but also aligns with Spatie’s documentation, ensuring you are implementing features in the recommended manner.

For example:

Utilized Laravel Breeze for authentication with frontend-designed Tailwind CSS and Spatie for role-based access control. Integrated Laravel Sanctum for secure token-based API and Laravel Multidomain middleware for simpler project integration. Enhanced security by hosting the admin panel on a separate domain (admin.solarneutrino.com) and (solarneutrino.com). Implemented a contact form with functionalities for email notifications, logging via Eloquent MySQL, and Telegram alerts. Infrastructure set up on EC2, utilizing Route 53 for DNS management and AWS SES for email dispatch.

]]>
https://t-jo.com/managing_roles_and_permissions_in_laravel_11_with_spatie_permission.html/feed 0
Node.js vs Go vs Laravel: 5 Advantages in Microservices Development https://t-jo.com/node-js_vs_go_vs_laravel_in_microservices.html https://t-jo.com/node-js_vs_go_vs_laravel_in_microservices.html#respond Fri, 05 May 2023 22:28:55 +0000 https://t-jo.com/?p=5728 Microservices architecture has become a go-to strategy for building scalable, maintainable, and flexible applications. When considering this architectural pattern, choosing the right technology stack is crucial. Three popular options are Node.js, Go, and Laravel. This article explores five key advantages each of these technologies offers in microservices development.

Node.js

Node.js is a JavaScript runtime built on Chrome’s V8 JavaScript engine. It offers several advantages in microservices development:

  • Non-Blocking I/O Model: Node.js excels at handling concurrent requests due to its event-driven, non-blocking I/O model. This makes it ideal for building real-time microservices with high throughput requirements.
  • Vast Ecosystem: The Node Package Manager (NPM) is one of the largest open-source ecosystems in the world. This gives developers access to thousands of libraries and tools that can accelerate microservices development.
  • JavaScript Everywhere: Node.js allows developers to use JavaScript for both server-side and client-side programming. This streamlines development and fosters better collaboration between frontend and backend teams.
  • Scalability: Node.js is designed to be highly scalable. With features like clustering and worker threads, it can efficiently distribute incoming requests across multiple processors, making it well-suited for scaling microservices.
  • Active Community and Support: Node.js has a vibrant and active community, which means robust support and regular updates. This ensures that issues are quickly resolved and new features are frequently introduced.

Go

Go, or Golang, is a statically typed, compiled programming language designed at Google. It brings several unique benefits to microservices:

  • Concurrency Model: Go’s goroutines and channels provide a simple yet powerful concurrency model. This makes it easy to build high-performing, concurrent microservices that can handle multiple tasks simultaneously.
  • Performance: Go is known for its speed and efficiency. Being a compiled language, it offers lower latency and higher throughput, making it excellent for performance-critical microservices.
  • Built-In Tools: Go comes with a rich set of built-in tools for testing, benchmarking, and profiling. This simplifies development and helps maintain high code quality in microservices projects.
  • Static Typing and Compile-Time Safety: Go’s static typing helps catch errors at compile time, leading to more reliable and maintainable code. This is particularly beneficial in large microservices projects where preventing runtime errors is crucial.
  • Simplicity and Clean Syntax: Go is designed for simplicity, which leads to clean, readable code. This is advantageous in microservices development, where maintainability and clarity are key.

Laravel

Laravel is a PHP framework known for its elegance and developer-friendliness. It provides several advantages for microservices:

  • Expressive Syntax: Laravel’s expressive and intuitive syntax simplifies development, allowing developers to focus on business logic rather than boilerplate code.
  • Rich Ecosystem: Laravel boasts a robust ecosystem of tools and libraries, such as Eloquent ORM and Blade templating. These tools accelerate microservices development by providing solutions for common challenges.
  • Built-In Features: Laravel comes with built-in features like routing, authentication, and caching, which are essential for microservices. These features simplify the development process and enhance productivity.
  • Scalability: Despite being known for monolithic applications, Laravel can be effectively used for microservices with the help of tools like Lumen (a micro-framework by Laravel). This provides a lightweight and scalable option for microservices architecture.
  • Developer Community: Laravel has a strong and active developer community. This means ample resources, tutorials, and support, which is beneficial for developing and maintaining microservices.

Conclusion

Choosing the right technology for microservices development depends on various factors, including team expertise, performance requirements, and project complexity. Node.js excels at handling concurrent requests with its non-blocking I/O model. Go offers superior performance and a robust concurrency model. Laravel provides a rich ecosystem and developer-friendly features.

Each technology has its own strengths and is suited to different scenarios. The choice ultimately hinges on the specific needs of your microservices architecture and the preferences of your development team.

Node.js vs Laravel vs Golang

]]>
https://t-jo.com/node-js_vs_go_vs_laravel_in_microservices.html/feed 0
Curl Benefits for Web Developers https://t-jo.com/curl_benefits_for_web_developers.html https://t-jo.com/curl_benefits_for_web_developers.html#respond Sun, 30 Apr 2023 18:18:03 +0000 https://t-jo.com/?p=5717 As a Web Developer, Here Are the TOP 10 Reasons Why «curl» Is Beneficial in an SSH Console Environment:

1. API and Web Service Testing 🧪:

Curl simplifies sending requests to APIs and web services, aiding in functionality testing and troubleshooting. It supports various request types (GET, POST, PUT, DELETE) and allows for transmitting different headers and data. Curl can also display detailed request and response information, which is invaluable for debugging.

2. File Downloading 📁:

Curl can download files from web servers, useful for retrieving configuration files, images, scripts, or other data. It can resume interrupted downloads, particularly helpful when dealing with large files.

3. Task Automation 🤖:

Curl can automate various tasks like file downloads, API requests, and web service testing. Scripts created with curl can perform repetitive tasks, saving time and effort. Its integration with other tools and programming languages makes curl a powerful tool for automation.

4. Information Gathering 🕵️‍♂️:

Curl can collect information from websites by extracting HTML, text, images, or other data. It can process JSON and XML responses, which is beneficial for collecting structured data.

5. Website and Server Monitoring 🖥:

Curl can monitor websites and servers by checking site availability, tracking server response times, and monitoring changes in web pages. It can also send notifications via email or other channels if it detects issues.

6. Simulation of User Agent and IP Spoofing 🕶:

Curl allows you to simulate different user agents and spoof IP addresses, which is critical when testing how APIs and websites respond to various browsers or geographic locations. This can be particularly useful for developers working on applications that need to behave differently based on user-agent or IP-based rules.

7. Security Testing 🔐:

Curl is a valuable tool for security testing of web applications. It can be used to send crafted requests that test for vulnerabilities like SQL injections, XSS, and CSRF, thereby helping developers strengthen their security measures before deployment.

8. Handling Cookies and Sessions 🍪:

Curl can manage cookies and sessions, allowing developers to mimic stateful sessions while interacting with web services. This is essential for testing applications that require authenticated sessions or tracking user interactions across multiple requests.

9. Network Diagnostics and Troubleshooting 🌐:

Curl provides detailed network diagnostics that help in identifying connectivity issues, slow response times, and other network-related problems. This can be invaluable for optimizing application performance and ensuring high availability.

10. Support for Multiple Protocols 📡:

Besides HTTP, Curl supports a wide range of other protocols including FTP, SMTP, LDAP, and more. This makes it a versatile tool for developers who need to interact with different network services during the development and testing phases.

CheatSheet — curl commands

My LinkedIn post

]]>
https://t-jo.com/curl_benefits_for_web_developers.html/feed 0
What a Full Stack Web Developer’s Portfolio Looks Like on Laravel + AWS Architecture with SEO Optimized Code and UX/UI Design https://t-jo.com/what-a-full-stack-web-developers-portfolio-looks-like-on-laravel-aws-architecture-with-seo-optimized-code-and-ux-ui-design.html https://t-jo.com/what-a-full-stack-web-developers-portfolio-looks-like-on-laravel-aws-architecture-with-seo-optimized-code-and-ux-ui-design.html#respond Thu, 09 Jun 2022 19:44:54 +0000 https://t-jo.com/?p=5736 Hey everyone! If you’ve ever wondered what a polished Full Stack Web Developer’s portfolio looks like, especially one that’s built on a robust Laravel + AWS architecture and enhanced with SEO-optimized code and sleek UX/UI design, then you’re in for a treat!

As we dive into the digital age, the significance of having a technically sound and visually appealing online portfolio cannot be overstated. Whether you’re a recruiter, a fellow developer, or someone interested in the tech world, understanding the components of an effective portfolio is key.

Seamless Integration with Laravel + AWS The combination of Laravel and AWS provides a powerful platform for developers to showcase their technical skills. Laravel’s elegant syntax and feature-rich framework paired with the scalable and secure infrastructure of AWS allows developers like me to build dynamic and robust applications. This setup not only demonstrates technical expertise but also a commitment to using industry-leading technologies.

SEO-Optimized Code In today’s competitive landscape, having an SEO-optimized portfolio is crucial. It ensures that the portfolio not only reaches its target audience but also ranks well on search engines. By integrating SEO strategies right from the coding phase, such as proper use of tags, optimized loading speeds, and mobile responsiveness, the portfolio stands out in Google searches, driving more organic traffic.

Cutting-edge UX/UI Design Aesthetics matter! A portfolio that’s easy on the eyes and offers an intuitive user experience can significantly boost engagement. Using the latest UX/UI design principles, the portfolio provides a seamless navigation experience, showcasing projects and skills in a format that’s both attractive and easy to digest.

Rich Content and Detailed Projects The portfolio highlights various projects with detailed descriptions of the tech stacks used and the challenges overcome. This not only reflects the scope of technical skills but also the problem-solving capabilities essential for a Full Stack Developer.

Ready to Explore More? You can check out my complete portfolio at Solar Neutrino. It’s designed to give you a comprehensive look at my work and the high standards I adhere to in every project. Also, don’t forget to check out the related LinkedIn post where I dive deeper into the functionalities and behind-the-scenes of my portfolio creation.

In conclusion, a well-crafted portfolio is more than just a showcase of projects; it’s a testament to a developer’s dedication to quality, innovation, and continuous learning. Whether you’re in the tech industry or simply have an appreciation for digital craftsmanship, understanding these elements can provide insights into the complexities and beauty of modern web development.

Looking forward to connecting with you and exploring potential collaborations that push the boundaries of technology and design!

 

]]>
https://t-jo.com/what-a-full-stack-web-developers-portfolio-looks-like-on-laravel-aws-architecture-with-seo-optimized-code-and-ux-ui-design.html/feed 0