<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
    <channel>
        <title>نوشته های علی علیمحمدی</title>
        <link>https://virgool.io/feed/@alinemone</link>
        <description>من یک Backend Developer هستم که دوست دارم توی مسیر یادگیری و تجربه رشد کنم. ساختن ، همیشه برام هیجان‌انگیزه و تلاش می‌کنم هر روز چیز جدیدی یاد بگیرم 🌱</description>
        <language>fa</language>
        <pubDate>2026-04-14 18:36:08</pubDate>
        <image>
            <url>https://files.virgool.io/upload/users/42564/avatar/j0pyJv.jpeg?height=120&amp;width=120</url>
            <title>علی علیمحمدی</title>
            <link>https://virgool.io/@alinemone</link>
        </image>

                    <item>
                <title>ساخت یک CLI برای مدیریت Port-Forwarding؛ از نیاز شخصی تا یک ابزار عمومی</title>
                <link>https://virgool.io/@alinemone/%D8%B3%D8%A7%D8%AE%D8%AA-%DB%8C%DA%A9-cli-%D8%A8%D8%B1%D8%A7%DB%8C-%D9%85%D8%AF%DB%8C%D8%B1%DB%8C%D8%AA-port-forwarding-%D8%A7%D8%B2-%D9%86%DB%8C%D8%A7%D8%B2-%D8%B4%D8%AE%D8%B5%DB%8C-%D8%AA%D8%A7-%DB%8C%DA%A9-%D8%A7%D8%A8%D8%B2%D8%A7%D8%B1-%D8%B9%D9%85%D9%88%D9%85%DB%8C-ozf1stche6qg</link>
                <description>توی پروژه‌های مختلفم همیشه یک دردسر تکراری داشتم: برای کار با چند سرویس و دیتابیس داخل Kubernetes باید مدام چندین تب ترمینال باز می‌کردم و برای هرکدام جداگانه port-forward می‌زدم. از همه بدتر، اگر یکی از این‌ها قطع می‌شد باید دوباره دستی رانش می‌کردم.این روند نه قابل مدیریت بود، نه مقیاس‌پذیر، نه حتی قابل اعتماد.به همین خاطر تصمیم گرفتم یک ابزار کوچیک ولی کاربردی با Go بنویسم؛ هم برای یادگیری بیشتر، هم برای اینکه کار روزمره‌ام ساده‌تر شود. نتیجه‌اش یک CLI ساده، ماژولار و قابل‌اتکا شد که این قابلیت‌ها را فراهم می‌کند:اضافه کردن بی‌نهایت سرویس برای port-forwardاجرای همزمان چندین forward فقط با یک دستورمشاهده‌ی وضعیت همه‌ی سرویس‌ها در یکجاReconnect خودکار در صورت قطع شدن کانکشنساختار تمیز، قابل‌گسترش و مناسب استفاده روزمرههدفم این بود که از این نقطه ضعف کوچک در جریان کاری‌ام، یک ابزار عمومی بسازم که شاید برای بقیه‌ی مهندس‌ها هم مفید باشد.در نهایت تصمیم گرفتم پروژه را اوپن‌سورس کنم تا اگر کسی خواست امکانات جدید اضافه کند، ایرادی پیدا کرد یا ایده‌ای داشت، بتوانیم هم‌افزایی کنیم.لینک پروژه و توضیحات بیشتر را در ریپوی گیت‌هاب گذاشته‌ام. خوشحال می‌شوم اگر استفاده کردید، نظر یا PR بگذارید.https://github.com/alinemone/go-port-forward</description>
                <category>علی علیمحمدی</category>
                <author>علی علیمحمدی</author>
                <pubDate>Sat, 29 Nov 2025 11:39:19 +0330</pubDate>
            </item>
                    <item>
                <title>از نیاز تا MVP : ساخت اکستنشن تبدیل گفتار به متن فارسی برای پشتیبانی باسلام</title>
                <link>https://experience.basalam.com/از-نیاز-تا-mvp-ساخت-اکستنشن-تبدیل-گفتار-به-متن-فارسی-برای-پشتیبانی-باسلام-sf2a6vzwis0p</link>
                <description>گاهی بهترین ابزارها از دل یک نیاز خیلی ساده شروع می‌شن.در تیم پشتیبانی باسلام، اپراتورها مدام باید متن‌های طولانی تایپ می‌کردن — از پاسخ به تیکت‌ها گرفته تا گفتگو با فروشنده‌ها.برای سرعت دادن به کار، ایده‌ی ساده‌ای مطرح شد:«کاش می‌شد با صدا حرف بزنیم و متن خودش نوشته بشه.»در ظاهر ساده بود، ولی وقتی خواستم از اکستنشن‌های آماده‌ی تبدیل گفتار به متن استفاده کنم، متوجه شدم تقریباً هیچ‌کدوم برای زبان فارسی قابل اتکا نیستن:یا فقط بخشی از واژه‌ها رو درست تشخیص می‌دادن،یا دکمه‌های عجیبی داشتن که تجربه‌ی کاربری رو خراب می‌کرد،یا اجازه‌ی سفارشی‌سازی نمی‌دادن.برای همین تصمیم گرفتم خودم یک اکستنشن سبک، دقیق و قابل سفارشی‌سازی برای زبان فارسی بسازم.بدون اینکه نیازی باشه محصول اصلی یا سیستم پشتیبانی باسلام تغییر کنه.چرا اکستنشن کروم؟به‌عنوان یک Backend Engineer اولین چیزی که بهش فکر می‌کنی API و سرویسه.ولی من دنبال یک MVP سریع بودم.نمی‌خواستم پروداکت یا فرانت پنل پشتیبانی باسلام رو درگیر کنم.پس اکستنشن بهترین گزینه بود چون:کاملاً ایزوله است.در هر صفحه‌ای که خواستی می‌تونه inject بشه.بدون نیاز به deploy یا backend جدید، کار می‌کنه.فقط کافیه کاربر اکستنشن رو نصب کنه و مجوز میکروفن بده.چطور ساختمش؟برای پیاده‌سازی تبدیل گفتار به متن، از مدل‌های Gemini گوگل استفاده کردم.مدل پایه‌ای که انتخاب کردم، gemini-2.5-flash-lite بود — چون رایگان، سریع، و برای MVP کاملاً کافی بود.ولی یه تصمیم مهم گرفتم که باعث شد پروژه از نظر مقیاس‌پذیری خیلی ساده‌تر بشه:به‌جای اینکه من سمت سرور API Key گوگل رو مدیریت کنم، توپ رو انداختم تو زمین کاربر!یعنی هر کاربر باید توکن شخصی گوگل AI خودش رو توی تنظیمات اکستنشن وارد کنه.به این ترتیب:نیازی به backend برای مدیریت توکن‌ها نداشتیم،محدودیت‌های نرخ درخواست (quota) بین کاربران تقسیم می‌شد،و حریم خصوصی بهتر حفظ می‌شد چون صدا مستقیم از مرورگر خودش به گوگل ارسال می‌شد.این تصمیم ساده، عملاً باعث شد اکستنشن بدون سرور هم قابل استفاده باشه — فقط با نصب و وارد کردن API Key.طراحی رفتار اکستنشندر ابتدا هدف این بود که برای تمام فیلدهای متنی (input و textarea) در هر صفحه،کنار فیلد یک دکمه‌ی میکروفن ظاهر بشه.ولی در عمل دیدم برای همه‌ی سایت‌ها این رفتار منطقی نیست.مثلاً توی بعضی صفحات نوشتاری یا فرم‌های حساس (مثل فیلد پسورد یا سرچ)،نباید دکمه ظاهر بشه.برای همین دو نوع فیلتر اضافه کردم:فیلتر دامنه (Domain Filter):فقط روی سایت‌هایی که کاربر در تنظیمات مشخص می‌کنه فعال بشه،مثلاً فقط روی basalam.com یا support.basalam.comفیلتر فیلد (Selector Filter):فقط روی عناصر خاص مثل textarea یا فیلدهایی با کلاس‌های مشخص(مثلاً .chat-input یا [data-voice-enabled]) دکمه اضافه بشه.تجربه‌ی شخصیوقتی نسخه‌ی MVP آماده شد و خود تیم فنی ازش استفاده کردیم،دقت مدل در تبدیل گفتار فارسی مارو واقعاً شگفت‌زده کرد.در حدی که تصمیم گرفتم خودم هم ازش برای نوشتن متن‌های طولانی استفاده کنم.به نظرم این یکی از اون لحظه‌هایی بود که می‌فهمی MVP اگر درست طراحی بشه، خودش یه محصول می‌تونه باشه.لینک پروژه : github</description>
                <category>علی علیمحمدی</category>
                <author>علی علیمحمدی</author>
                <pubDate>Thu, 16 Oct 2025 11:49:30 +0330</pubDate>
            </item>
                    <item>
                <title>چگونه با استفاده از Metabase SDK و FastAPI، دسترسی امن و محدود به داده‌های داشبوردها ایجاد کردیم؟</title>
                <link>https://experience.basalam.com/چگونه-با-استفاده-از-metabase-sdk-و-fastapi-دسترسی-امن-و-محدود-به-داده-های-داشبوردها-ایجاد-کردیم-da98zns7hsin</link>
                <description>در تیم پشتیبانی ، نیاز داشتیم اپراتورها فقط به داده‌هایی خاص از داشبوردهای متابیس دسترسی داشته باشن — نه بیشتر.ما نمی‌خواستیم:کاربر وارد محیط متابیس بشه،چیزی از کوئری‌ها ببینه،یا حتی به لینک یا iframe داشبوردها دسترسی داشته باشه.🎯 راه‌حل نهایی: React + Metabase SDK + FastAPI Proxyدر نهایت، این معماری رو پیاده‌سازی کردیم:در سمت فرانت‌اند، از Metabase Embedding SDK استفاده کردیم.این SDK امکان رندر مستقیم داشبورد داخل کامپوننت‌های ری‌اکت رو می‌ده.اما برای امنیت بیشتر، جلوی ارتباط مستقیم SDK با متابیس رو گرفتیم و یک لایه پروکسی با FastAPI بینشون قرار دادیم.جریان کار به این شکله:کاربر وارد پنل پشتیبانی می‌شه (احراز هویت با JWT داخلی ما).کاربر تنها داشبوردهای رو می‌بینه که با توجه به رول/پرمیشن‌هاش در سیستم تعریف شدن.وقتی SDK می‌خواد داده‌ی یک داشبورد رو بگیره، درخواست رو به جای متابیس، به سرویس پروکسی ما می‌فرسته.در پروکسی (FastAPI):JWT اعتبارسنجی می‌شه.پرمیشن کاربر برای این داشبورد چک می‌شه.در صورت تایید، درخواست به API متابیس فوروارد می‌شه.پاسخ متابیس به‌صورت JSON دریافت می‌شه و به SDK برگشت داده می‌شه.SDK داده رو گرفته و خودش داخل ری‌اکت رندر می‌کنه .چرا SDK رو به‌جای embed URL انتخاب کردیم؟کنترل کامل روی ظاهر و تعامل در ری‌اکتبدون استفاده از iframe و محدودیت‌هایشامکان شخصی‌سازی request ها و header هاخروجی JSON آماده برای ویژوال‌سازی دلخواهنکات امنیتی مهمارتباط مستقیم بین کاربر و متابیس کاملاً قطع شده.فقط پروکسی ما به API متابیس دسترسی داره.سطح دسترسی هر کاربر قبل از ارسال request به متابیس، در سرویس ما بررسی می‌شه.داده‌ی خام، کوئری‌ها یا اطلاعات متا به کاربر داده نمی‌شه؛ فقط خروجی پاک‌شده و کنترل‌شده از متابیس.نمونه کد سمپل پروکسی سرویس :from fastapi import FastAPI, Request, HTTPException
from fastapi.responses import JSONResponse, Response
import httpx
import json
from urllib.parse import urlencode

app = FastAPI()

METABASE_URL = &quot;https://your-metabase-url.com&quot;  # ← آدرس متابیس
METABASE_API_KEY = &quot;your-api-key&quot;               # ← کلید API متابیس


@app.api_route(&quot;/{path:path}&quot;, methods=[&quot;GET&quot;, &quot;POST&quot;, &quot;OPTIONS&quot;])
async def proxy_metabase(path: str, request: Request):
    # ⛔️ اینجا می‌تونی دسترسی‌های کاربر رو بررسی کنی
    # --------------------------------------------------------------------
    # مثال:
    # user = await get_current_user(request)
    # if not user.has_permission(path):
    #     raise HTTPException(status_code=403, detail=&quot;Permission denied&quot;)
    # --------------------------------------------------------------------

    # ساخت آدرس کامل متابیس
    query_string = urlencode(dict(request.query_params))
    full_url = f&quot;{METABASE_URL}/{path}&quot;
    if query_string:
        full_url += f&quot;?{query_string}&quot;

    # گرفتن بدنه درخواست در صورت نیاز
    body = None
    if request.method in {&quot;POST&quot;, &quot;PUT&quot;, &quot;PATCH&quot;}:
        raw = await request.body()
        if raw:
            body = json.loads(raw.decode(&quot;utf-8&quot;))

    # ارسال درخواست به متابیس
    async with httpx.AsyncClient() as client:
        response = await client.request(
            method=request.method,
            url=full_url,
            json=body,
            headers={
                &quot;x-api-key&quot;: METABASE_API_KEY,
                &quot;User-Agent&quot;: &quot;fastapi-proxy&quot;
            }
        )

    # بازگرداندن پاسخ
    content_type = response.headers.get(&quot;content-type&quot;, &quot;&quot;)
    if &quot;application/json&quot; in content_type:
        return JSONResponse(content=response.json(), status_code=response.status_code)
    return Response(
        content=response.content,
        status_code=response.status_code,
        media_type=content_type
    )
سمت فرانت هم به این شکل عمل کردیم:📁 ساختار پیشنهادیsrc/
├── metabase/
│   ├── MetabaseProvider.jsx
│   ├── Dashboard.jsx
│   └── config.js
✅ config.js (تنظیمات اتصال به متابیس)import { defineMetabaseAuthConfig, defineMetabaseTheme } from &#039;@metabase/embedding-sdk-react&#039;

const config = defineMetabaseAuthConfig({
  preferredAuthMethod: &#039;jwt&#039;,
  metabaseInstanceUrl: &#039;https://proxy-api.example.com&#039;, // آدرس متابیس پروکسی شما

  // درخواست گرفتن توکن JWT از API بک‌اند
  fetchRequestToken: async () =&gt; {
    const response = await fetch(&#039;https://proxy-api.example.com/auth/sso?preferred_method=jwt&#039;, {
      method: &#039;GET&#039;,
      credentials: &#039;include&#039;, // ← برای ارسال کوکی‌ها
    })

    return response.json() // باید { token: &#039;...&#039; } برگردونه
  },
})

// تم اختیاری (می‌تونید حذفش کنید)
const theme = defineMetabaseTheme({ colorScheme: &#039;light&#039; })

export { config, theme }✅ MetabaseProvider.jsx (Provider اصلی برای کانتکست)import { config, theme } from &#039;./config&#039;
import { MetabaseProvider as SDKProvider } from &#039;@metabase/embedding-sdk-react&#039;

function MetabaseProvider({ children }) {
  return (
    &lt;SDKProvider authConfig={config} theme={theme}&gt;
      {children}
    &lt;/SDKProvider&gt;
  )
}

export { MetabaseProvider }
✅ Dashboard.jsx (نمایش داشبورد خاص با پارامتر)import { useMemo } from &#039;react&#039;
import { useLocation, useParams } from &#039;react-router-dom&#039;
import { StaticDashboard, MetabaseProvider } from &#039;@metabase/embedding-sdk-react&#039;

function objectifyQuery(search) {
  const query = new URLSearchParams(search)
  const obj = {}
  for (const [key, value] of query.entries()) {
    obj[key] = value
  }
  return obj
}

function DashboardPage() {
  const { id } = useParams() // مثلاً /dashboard/12
  const { search } = useLocation()
  const queryParams = useMemo(() =&gt; objectifyQuery(search), [search])

  return (
    &lt;div style={{ height: &#039;100vh&#039; }}&gt;
      &lt;MetabaseProvider&gt;
        &lt;StaticDashboard
          dashboardId={+id}
          initialParameters={queryParams}
          withTitle
        /&gt;
      &lt;/MetabaseProvider&gt;
    &lt;/div&gt;
  )
}

export default DashboardPage
نتیجه‌گیریاگر به دنبال پیاده‌سازی یک سیستم امن و حرفه‌ای برای نمایش داشبوردهای متابیس هستید که:دسترسی‌ها و پرمیشن‌ها را به صورت دقیق و مبتنی بر نقش کاربران مدیریت کند،بدون استفاده از iframe و با حفظ یکپارچگی تجربه کاربری در فرانت‌اند ری‌اکت باشد،کوئری‌ها و لینک‌های حساس متابیس را از دید کاربران نهایی مخفی نگه دارد،و قابلیت توسعه و سفارشی‌سازی آسان را داشته باشد،ترکیب استفاده از بسته @metabase/embedding-sdk همراه با یک سرویس پروکسی امن مبتنی بر FastAPI، راهکاری ایده‌آل، مقیاس‌پذیر و قابل اعتماد خواهد بود که به راحتی می‌توانید آن را مطابق نیازهای سازمان خود توسعه دهید و بهینه‌سازی کنید.نمونه داشبورد پیاده سازی شده :</description>
                <category>علی علیمحمدی</category>
                <author>علی علیمحمدی</author>
                <pubDate>Mon, 28 Jul 2025 11:49:47 +0330</pubDate>
            </item>
                    <item>
                <title>دیپلوی (نصب) پروژه لاراول روی هاست اشتراکی</title>
                <link>https://virgool.io/@alinemone/install-laravel-on-host-vbelp0h1cxbc</link>
                <description>احتمالا شما هم اموزش های نصب لاراول با تغییر ساختار پروژه لاراولی رو دیدید اما به شخصه از این که ساختار پروژه تغییر بدیم دوست نداشتم به همین دلیل ی راه ساده تر پیشنهاد میکنم اونم استفاده از فایل .htaccess هستش .برای دیپلوی پروژه ، کل محتوای پروژتون را داخل پوشه public_html بریزید و در کنار فایل .env یک فایل htaccess بسازید و محتوای زیر رو داخلش بزارید&lt;IfModule mod_rewrite.c&gt;    RewriteEngine On    RewriteCond %{REQUEST_URI} !^public    RewriteRule ^(.*)$ public/$1 [L]&lt;/IfModule&gt;حالا بدون تغییر ساختار میتونید پروژتون را مشاهده اش کنید.شاد و پیروز باشید</description>
                <category>علی علیمحمدی</category>
                <author>علی علیمحمدی</author>
                <pubDate>Thu, 01 Dec 2022 10:57:47 +0330</pubDate>
            </item>
                    <item>
                <title>تکرار درخواست به api در صورت مشکل | retry python request</title>
                <link>https://virgool.io/@alinemone/retry-python-request-ewzlpiy9jpjy</link>
                <description>بعد از مدت ها اینجا مطلبی به اشتراک میزارم اونم به خاطر این که فکر میکنم مطلب به درد بخوری ممکنه باشهتو یکی از پروژه های شرکت #باسلام نیاز بود که توکن از سرویسی گرفته بشه و درصورت اکسپایر شدن دوباره اینکار تکرار کنه.چالش ها :۱- نبود دیتابیس ۲- در بهینه ترین شرایط استفاده از منابع سرور باشهدر شروع :پروژه ابتدا با یک اسکژول هر n دقیقه توکن جدید دریافت میکرد و در redis ذخیره میکرد. این خوب بود اما اگر درخواست به خطا میخورد چی ؟ نباید درخواست از بین میرفت  :/ فعلا اینو ایگنور میکنیم تا بعد :) پروژه بعد از ۳ هفته تکمیل شد حالا نوبت این رسید که قسمتی که ایگنور کردیم هندل کنم ، اوه تو چندین فایل رکویست های جدا زده شده !راه حل پیشنهادی چیه ؟ی فایل اصلی بسازیم که متد رکویست داخل اون هندل بشه و بقیه از اون ارث بری کنند و تابع رکویست پدر فراخوانی کنند تا اینجاش خوب بود ولی بازم رکویست در صورت پاسخ ندادن api به فنا میرفت .رفع مشکل اصلی :باید ساختاری ایجاد میشد که بتونیم درخواست تکرار کنیم . پس درخواستُ داخل یه حلقه while قرار میدیم اگر درخواست اوکی بود که رسپانس دریافت میکنیم اما اگر خطا داشت داخل while خود تابع صدا میکنیم اما به دیتا های ورودی قبل دسترسی نداریم پس داخل تابع اصلی مقادیر تو متغیر های سراسری دیگه ای ذخیره میکنیم که در صورت به فنا رفتن رکویست به دیتا ها دسترسی داشته باشیم. با این کار تا بینهایت میتونیم درخواست تکرار کنیم اما نباید با تکرار زیاد منابع سروری که بهش رکویست میزنیم هم پر کنیم پس نیاز به ی ماکسیمم داریم که فقط n بار درخواستمون تکرار کنیم اگر به اون عدد رسیدیم باید break کنیم حالا میشه این خطا هارو نسبت به هر کدومش اکشن متفاوت داشت به عنوان مثال اگر استاتوس 401 گرفتیم پس باید توکن جدید بگیریم و درخواست بعد از دریافت توکن تکرار کنیم یا اگر اکسپشن گرفتیم خطا مون کپچر کنیم برای من نتیجه خیلی جالبی داشت چون کلی از تکرار کد جلوگیری کرد و هم ی کار چالش دار جدید بودآدرس ریپازیتوری : github</description>
                <category>علی علیمحمدی</category>
                <author>علی علیمحمدی</author>
                <pubDate>Wed, 30 Nov 2022 19:05:38 +0330</pubDate>
            </item>
                    <item>
                <title>ورود با شماره یا ایمیل در لاراول 8 - login by phone or email laravel 8</title>
                <link>https://virgool.io/@alinemone/%D9%88%D8%B1%D9%88%D8%AF-%D8%A8%D8%A7-%D8%B4%D9%85%D8%A7%D8%B1%D9%87-%DB%8C%D8%A7-%D8%A7%DB%8C%D9%85%DB%8C%D9%84-%D8%AF%D8%B1-%D9%84%D8%A7%D8%B1%D8%A7%D9%88%D9%84-8-login-by-phone-or-email-laravel-8-zryiwlpqzdsc</link>
                <description>Login to Laravel 8 by phone or emailدر لاراول 8 از اونجایی که خودتونم میدونید سیستم Auth با پکیج جت استریم ایجاد شده و در ابتدا شمارو دچار سردرگمی میکنه چون به ظاهر هیچ لاگین کنترلری وجود نداره. پس ما از کجا ورود به وب سایت شخصی سازی کنیم ؟حالا میخواهیم کاری کنیم که هم با ایمیل و هم با شماره موبایل بتونیم وارد سایت شویم تنها کاری که میکنیم ستون جدیدی به جدول یوزر با عنوان phone اضافه میکنیم . و موقع ثبت نام یا هر جای دیگه میتونید این فیلد را پر کنید .اما بحث اصلی ، چطوری  کاربر به دلخواه خودش با ایمیل یا شماره تلفن بتونه وارد سایت بشه ابتدا باید داخل صفحه لاگین type اینپوت ایمیل را به text تغییر دهید . بعد از آن به داخل پوشه app\Providers شوید و در فایل FortifyServiceProvider متد boot را ویرایش کنید قطعه کد زیر را انتهای متد boot اضافه کنید.Fortify::authenticateUsing(function (Request $request) {
if (preg_match(&amp;quot/[-0-9a-zA-Z.+_]+@[-0-9a-zA-Z.+_]+.[a-zA-Z]{2,4}/&amp;quot, $request-&gt;email )){
    $user = User::where(&#039;email&#039;, $request-&gt;email)-&gt;first();
}
if(preg_match(&amp;quot/09(1[0-9]|3[1-9]|2[1-9])-?[0-9]{3}-?[0-9]{4}/&amp;quot, $request-&gt;email)){
        $user = User::where(&#039;phone&#039;, $request-&gt;email)-&gt;first();
}
if ($user &amp;&amp; Hash::check($request-&gt;password, $user-&gt;password)) {
        return $user;
}
});در این کد ابتدا ما فرمت رکویست ایمیل را چک میکنیم که به صورت ایمیل هست یا شماره و به نسبت اون کاربر پیدا میکنیم و بعد از پیدا شدن کاربر پسورد کاربر با پسورد ورودی از فرم لاگین را چک میکنیم اگر پسورد ها یکسان باشند کاربر ارسال میشه و ورود به سایت انجام میشه </description>
                <category>علی علیمحمدی</category>
                <author>علی علیمحمدی</author>
                <pubDate>Mon, 23 Nov 2020 17:45:56 +0330</pubDate>
            </item>
                    <item>
                <title>افزودن متد جدید به ریسورس کنترلر به صورت داخلی + Route</title>
                <link>https://virgool.io/@alinemone/%D8%A7%D9%81%D8%B2%D9%88%D8%AF%D9%86-%D9%85%D8%AA%D8%AF-%D8%AC%D8%AF%DB%8C%D8%AF-%D8%A8%D9%87-%D8%B1%DB%8C%D8%B3%D9%88%D8%B1%D8%B3-%DA%A9%D9%86%D8%AA%D8%B1%D9%84%D8%B1-%D8%A8%D9%87-%D8%B5%D9%88%D8%B1%D8%AA-%D8%AF%D8%A7%D8%AE%D9%84%DB%8C-jueei6jygt5c</link>
                <description>روش های ساده ای در اینترنت وجود داره مثل نوشتن یک روت معمولی قبل روت ریسورس و استفاده از آن در همان کنترولر  مانند شکل زیر Route::get(&#039;tag/details&#039;,[TagController::class,&#039;details&#039;]);
Route::resource(&#039;tag&#039;,TagController::class);اما در این آموزش میخواهم تغییری در لاراول ایجاد کنیم که با دستور php artisan make:controller  ، کنترلر ریسورس ایجاد کنیم که علاوه بر متد هایی که به صورت دیفالت دارد متد جدیدی هم به صورت اتوماتیک برای ما بسازد.در اولین قدم باید با دستور زیر stub های لاراول را جهت تغییر ایجاد کنیم php artisan stub:publishبا زدن این دستور پوشه ای با نام stubs در پروژه لاراول ایجاد خواهد شد . فایلی که می بایست آن را تغییر داد controller.stub است .در این فایل متدی که میخواهید در همه کنترلر های ریسورس وجود داشته باشد را اضافه کنید. مانند زیر //controller.stub
&lt;?php

namespace {{ namespace }};

use {{ rootNamespace }}Http\Controllers\Controller;
use Illuminate\Http\Request;

class {{ class }} extends Controller
{
    /**
     * Display a listing of the resource.
     *
     * @return \Illuminate\Http\Response
     */
    public function index()
    {
        //
    }

    /**
     * Show the form for creating a new resource.
     *
     * @return \Illuminate\Http\Response
     */
    public function create()
    {
        //
    }

    /**
     * Store a newly created resource in storage.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\Response
     */
    public function store(Request $request)
    {
        //
    }

    /**
     * Display the specified resource.
     *
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function show($id)
    {
        //
    }

    /**
     * Show the form for editing the specified resource.
     *
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function edit($id)
    {
        //
    }

    /**
     * Update the specified resource in storage.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function update(Request $request, $id)
    {
        //
    }

    /**
     * Remove the specified resource from storage.
     *
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function destroy($id)
    {
        //
    }



     public function details()
    {
        //
    }
}در این فایل من متد details را در انتهای فایل اضافه کرده ام ، تا اینجای کار اگر دستور ارتیسان جهت ایجاد کنترلر ریسورس را وارد کنید متد details نیز برای شما ایجاد خواهد شد اما روت مربوط به این متد هنوز وجود ندارد.برای ایجاد روت این متد داخل دایرکتوری app پوشه ای به نام دلخواد که در اینجا من Routing قرار داده ام ایجاد کنید . فایلی هم با نام دلخواه ایجاد ، (CustomResourceRegistrar) و داخل این فایل از کلاس ResourceRegistrar ارث بری کنید .//CustomResourceRegistrar
&lt;?php

namespace App\Routing;

use Illuminate\Routing\ResourceRegistrar;

class CustomResourceRegistrar extends ResourceRegistrar
{

    protected $resourceDefaults =  [
        &#039;details&#039;,
        &#039;index&#039;,
        &#039;create&#039;,
        &#039;store&#039;,
        &#039;show&#039;,
        &#039;edit&#039;,
        &#039;update&#039;,
        &#039;destroy&#039;,

    ];

    protected function addResourceDetails($name, $base, $controller, $options)
    {
        $uri = $this-&gt;getResourceUri($name).&#039;/details&#039;;

        $action = $this-&gt;getResourceAction($name, $controller, &#039;details&#039;, $options);

        return $this-&gt;router-&gt;get($uri, $action);
    }

}برای شناسایی متد details میبایست resourceDefaults را باز نویسی کرد .برای ایجاد روت details متد جدیدی همنام با متد اصلی ایجاد میکنیم ادرسی که میخواهید در مرورگر به این متد دسترسی داشته باشید اضافه کرده و کار ما اینجا تمام است . تنها کاری که لازم است ، شناسایی کلاس CustomResourceRegistrar به لاراول است . برای این کار در فایل AppServiceProvider در متد register کد زیر را اضافه کنید.public function register()
{
    $this-&gt;app-&gt;bind(
        &#039;Illuminate\Routing\ResourceRegistrar&#039;, // original class 
        &#039;App\Routing\CustomResourceRegistrar&#039; // Your custom class
    );
}کار ما تمام شد حالا میتوانید با دستور ارتیسان کنترلر های ریسورسی ایجاد کنید که در همه آن ها متد جدیدی با نام details وجود دارد.php artisan make:controller TagController -r</description>
                <category>علی علیمحمدی</category>
                <author>علی علیمحمدی</author>
                <pubDate>Mon, 23 Nov 2020 11:16:40 +0330</pubDate>
            </item>
                    <item>
                <title>تبدیل قالب های bootstrap به Tailwind</title>
                <link>https://virgool.io/@alinemone/%D8%AA%D8%A8%D8%AF%DB%8C%D9%84-%D9%82%D8%A7%D9%84%D8%A8-%D9%87%D8%A7%DB%8C-bootstrap-%D8%A8%D9%87-tailwind-vnhyv6tdcjqu</link>
                <description>همانطور که اطلاع دارید در ورژن جدید لاراول یعنی ورژن 8 دیگر خبری از بوت استرپ نیست و از فریم ورک css جدیدی با نام tailwind استفاده شده و مشکلی که برای برنامه نویس ها پیش اومده اینه که از این فریم ورک چطور استفاده کنند که به طبع باید اون یاد بگیرن . به نظر من با این ابزار که در زیر معرفی کردم یاد گیری اون راحت میشه ...امروز پکیج جالبی پیدا کردم که میتونید قالب های بوت استرپ به Tailwind تبدیل کنید اسم این پکیج Tailwindo هست که ادرس  داکیومنت آن در پایین قرار داده شده :https://awssat.com/opensource/tailwindo/برای استفاده از پکیج ابتدا باید آن را به صورت global در سیستم خود نصب کنید:composer global require awssat/tailwindoبعد از نصب میتوانید به شکل زیر فایل های بلید خود را از بوت استرپ به Tailwind تبدیل کنید:cd ~/my-project 
tailwindo resources/views --extensions=php --recursive=true --replace=trueهمچنین Tailwindo می تواند فایل های Vue.js و HTML را تبدیل کند:tailwindo resources/assets/js/components --extensions=vue --recursive=true --replace=trueاگر می خواهید در آن واحد تنها یک فایل را تبدیل کنید، می توانید این کد را اجرا کنید:tailwindo file.blade.php</description>
                <category>علی علیمحمدی</category>
                <author>علی علیمحمدی</author>
                <pubDate>Thu, 22 Oct 2020 19:22:21 +0330</pubDate>
            </item>
                    <item>
                <title>ایجاد لاگین کنترلر و شخصی سازی آن در لاراول 8</title>
                <link>https://virgool.io/@alinemone/%D8%A7%DB%8C%D8%AC%D8%A7%D8%AF-%D9%84%D8%A7%DA%AF%DB%8C%D9%86-%DA%A9%D9%86%D8%AA%D8%B1%D9%84%D8%B1-%D9%88-%D8%B4%D8%AE%D8%B5%DB%8C-%D8%B3%D8%A7%D8%B2%DB%8C-%D8%A2%D9%86-%D8%AF%D8%B1-%D9%84%D8%A7%D8%B1%D8%A7%D9%88%D9%84-8-f1pph9g3rnia</link>
                <description>برای این کار ابتدا نیاز به ساخت یک کنترلر داریم که با دستور زیر ان را در پوشه http/controllers/Authمیسازیمphp artisan make:controller Auth\LoginControllerو مقادیر زیر را در آن قرار میدهیم :&lt;?php

namespace App\Http\Controllers\Auth;

use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;

class LoginController extends Controller
{
    public function login(Request $request)
    {
 
        $credentials = $request-&gt;only(&#039;email&#039;, &#039;password&#039;);

        if (Auth::attempt($credentials)) {
            // if success login
            return redirect(&#039;/&#039;);
        }
        // if failed login
        return redirect(&#039;login&#039;);
    }
}بعد از این نیاز به تعریف route داریم که در فایل web.php آن را تعریف میکنیم Route::post(&#039;logged_in&#039;, [LoginController::class, &#039;login&#039;])-&gt;name(&#039;login&#039;);با این کار لاگین کنترلر خودتون رو ساختید و بدون تغییر ویو مربوط به لاگین jetstream از اون استفاده کنید</description>
                <category>علی علیمحمدی</category>
                <author>علی علیمحمدی</author>
                <pubDate>Thu, 24 Sep 2020 17:26:07 +0330</pubDate>
            </item>
                    <item>
                <title>معرفی inertia js  در لاراول 8 برای ایجاد وبسایت های SPA + ویدیو</title>
                <link>https://virgool.io/@alinemone/%D9%85%D8%B9%D8%B1%D9%81%DB%8C-inertia-js-%D8%AF%D8%B1-%D9%84%D8%A7%D8%B1%D8%A7%D9%88%D9%84-8-%D8%A8%D8%B1%D8%A7%DB%8C-%D8%A7%DB%8C%D8%AC%D8%A7%D8%AF-%D9%88%D8%A8%D8%B3%D8%A7%DB%8C%D8%AA-%D9%87%D8%A7%DB%8C-spa-%D9%88%DB%8C%D8%AF%DB%8C%D9%88-mgzywtgfoatv</link>
                <description>معرفی پکیج inertia js و کاربرد آن :پکیج inertia js ، پکیجی برای استفاده یکپارچه از قابلیت های SPA با استفاده از Back End طراحی شده توسط شماست ، ولی این یعنی چی ؟ شاید فکر کنید که Back End چگونه میتواند با Front End در ارتباط باشد خصوصا در حالت SPA ؟!جواب آن را در توضیحی که از سوی شرکت سازنده ارائه شده است خواهید یافت : این پکیج هیچ مسیریابی طرف مشتری ندارد و همچنین به API احتیاج ندارد. نگران نباشید ، کنترلرها و ویوهای صفحه مانند همیشه کار خود را انجام میدهند!شاید شما هم از متن بالا متوجه شده باشید که قرار نیست API یا وبسرویسی برای ارتباط بین Front End  و  Back End بنویسید و تنها کافیست با همان روش سابق ( نمونه ی زیر )  به کد نویسی خود ادامه دهید و باقی کار را به inertia بسپارید و این یعنی لذت بیشتر در طراحی سایت با لاراول دوست داشتنی و Vue js قدرتمند !در تصویر بالا ،‌ کدها بصورت معمول دریافت و return میشود و تنها تفاوت آن ، استفاده از کلاس Inertia و متد render است که با این کد شما پوشه Users و فایل index.vue را به آن پاس میدهید و دیگر کاری با لاراول ندارید و در ادامه ، برای نمایش این کد ها به صورت زیر عمل میکنید :مزیت دیگر inertia js این است که بصورت اتوماتیک کامپوننت های شما را شناسایی میکند ، برای این کار در لاراول 8 پوشه ای به نام pages اضافه شده است (البته بعد از نصب پکیج jetstream و نصب پکیج inertia به کمک دستور ارتیسان jetstream) که تمام کامپوننت های شما در آن قرار میگیرد . حال بطور مثال اگر برای نمایش - ایجاد و ویرایش کاربران خود نیاز به ایجاد کامپوننت داشته باشید به شکل زیر میبایست عمل کنید.داخل پوشه pages پوشه دیگری به نام Users ایجاد شده و کامپوننت های نمایش همه کاربران (index.vue) ایجاد و ویرایش کاربران قرار داده شده است  https://www.aparat.com/v/pNBqJ </description>
                <category>علی علیمحمدی</category>
                <author>علی علیمحمدی</author>
                <pubDate>Wed, 09 Sep 2020 11:39:02 +0430</pubDate>
            </item>
                    <item>
                <title>نصب و راه اندازی جنگو django مثل آب خوردن</title>
                <link>https://virgool.io/@alinemone/%D9%86%D8%B5%D8%A8-%D9%88-%D8%B1%D8%A7%D9%87-%D8%A7%D9%86%D8%AF%D8%A7%D8%B2%DB%8C-%D8%AC%D9%86%DA%AF%D9%88-django-%D9%85%D8%AB%D9%84-%D8%A2%D8%A8-%D8%AE%D9%88%D8%B1%D8%AF%D9%86-c3ndy1sj5rmi</link>
                <description>از اونجایی که فریم ورک جنگو تازه شروع کردم تا بفهمم اصلا چی به چی هست اولش خیلی سر در نیاوردم که این محیط مجازی که بهش میگن virtualenv چی هست تا کم کم فهمیدم همون قسمتی هستش که پکیج های داخلی مارو نصب میکنه بعضی وبسایت ها خیلی گنگ تعریفش کردند اما من به زبون خودمونی اینطوری میگم که شما هر پکیجی که میخواید توی پروژتون استفاده کنید باید ی جایی نصب بشه دیگه این محیط مجازی هم همین کار میکنه که میاد پکیج هایی که نیاز دارید براتون تو همین محیط ایزوله نصب میکنه !! ممکنه قبلا به طور مثال ورژن 2 یه پیکیج به صورت گلوبال تو کل سیستم نصب کرده باشید ولی الان نیاز به ورژن پایین تر اون داشته باشید اینطوری اگر اون حذف کنید و ورژن پایین ترش نصب کنید شاید خیلی از پروژه های دیگتون به مشکل بخوره برای همین تو این محیط مجازی میاید اون ورژن مد نظرتون نصب میکنید.خب حالا بریم سر اصل مطلب :البته این اموزشی که دارم میزارم بعد از نصب پایتون virtualenv هستش ! :)برای نصب جنگو یه پوشه ایجاد کنید به اسم پروژتون بعد با cmd به داخل این پوشه برید بعد از ورود به پوشه دستور زیر را وارد کنید virtualenv venvبا این کار پوشه ای به اسم venv داخل پوشه پروژتون ایجاد میشه حالا محیط ایزوله ما اماده است اما باید اون اکتیو کنیم برای اکتیو کردن اون باید دستور زیر اجرا کنیدcd venv/Scripts/activateبا این کار متن (venv) به شکل زیر به کامند شما اضافه خواهد شد این به معنی است که محیط مجازی یا همون ایزوله شما اماده است و میتونید جنگو رو نصب کنید با دستور زیر جنگو برای ما نصب میشه pip install Djangoاز اونجایی که من خودمم فکر میکردم با این دستور دیگه حله پروژه اماده است اما هنوز نصب کامل نشده تا اینجا ما فقط جنگو به صورت پکیج نصب کردیم . حالا میتونید با دستور pip freezeپکیج هایی که نصب شده رو ببینید برای ایجاد پروژه جنگو باید در cmd به پوشه اصلی پروژه که اول ساختید بیاید تا پروژتون در کنار پوشه venv ایجاد بشه یعنی اگر پوشه در دسکتاپ باشه یه همچین ادرسی میشه (venv) C:\Users\user\Desktop\projectDjangoحالا در این ادرس دستور زیر را در cmd وارد میکنیم django-admin startproject projectکه اون project اخر دلبخواه خودتونه اسم پوشه ای هست که جنگو در اون نصب میشه .حالا با cd در کامند وارد پوشه project میشیم و دستور زیر را وارد میکنیم python manage.py migrateبا این دستور دیتا بیس پیشفرض و جداول خود جنگو برای ما در دیتا بیس sqlite کنار پروژه ایجاد میشه بعد از این هم نوبت به استارت کردن سرور و دیدن سایت جنگویی خودمون میرسیم . python manage.py runserverبا این دستور بالا سایت ما در ادرس 127.0.0.1:8000 قابل دسترس هستش . نمونه پوشه های موجود در پروژه جنگواین اموزش با یافته ها و تجربه ابتدایی خودم که اصلا هیچ پیش زمینه ای در پایتون بود نوشته شده اگر اساتید بزرگوار با دیدن این اموزش اشکالاتی رو دیدند لطفا اطلاع بدید. </description>
                <category>علی علیمحمدی</category>
                <author>علی علیمحمدی</author>
                <pubDate>Sat, 18 Jul 2020 10:21:48 +0430</pubDate>
            </item>
                    <item>
                <title>ایجاد جستجو زنده (ایجکس) با Vue و لاراول</title>
                <link>https://virgool.io/laravel-community/%D8%A7%DB%8C%D8%AC%D8%A7%D8%AF-%D8%AC%D8%B3%D8%AA%D8%AC%D9%88-%D8%B2%D9%86%D8%AF%D9%87-%D8%A7%DB%8C%D8%AC%DA%A9%D8%B3-%D8%A8%D8%A7-vue-%D9%88-%D9%84%D8%A7%D8%B1%D8%A7%D9%88%D9%84-jlqnsdnaflk0</link>
                <description>نتیجه کاربه شخصه خیلی دنبال همچین جستجویی گشتم و تو رفرنس های فارسی چیز به درد بخوری پیدا نکردم تا این که به پکیج spatie/laravel-searchable رسیدم کار باهاش خیلی اسونه و فقط کافیه مدلی که میخوایم جستجو بشه تریتشو یوز کنیم بریم سر اصل مطلب ابتدا پکیج spatie/laravel-searchable را با دستور زیر نصب کنید:composer require spatie/laravel-searchableبعد از نصب داخل مدل مورد نظرتون برید : &lt;?phpnamespace App;use Spatie\Searchable\Searchable;
use Spatie\Searchable\SearchResult;
use Illuminate\Database\Eloquent\Model;class Product extends Model implements Searchable
{
    public function getSearchResult(): SearchResult
    {       $url = $this-&gt;path();
         
       return new SearchResult($this, $this-&gt;title, $url);
    }    public function path(){        return $this-&gt;slug;    }
}

خب همانطور که میبینید ما از implements Searchable استفاده کردیم و یک متد هم به صورت اتوماتیک برای ما اضافه میشه .حالا نیاز به یک rote داریم که اطلاعات باهاش به کنترلر بفرستیم Route::get(&#039;product/search&#039;, &#039;SearchController@index&#039;);بعد از ایجاد مسیر ، کنترلر مربوطه هم با دستور زیر میسازیم:php artisan make:controller SearchController
بعد از ایجاد کنترلر کد های زیر را در ان قرار میدهیم&lt;?php
namespace App\Http\Controllers;
use App\Product;
use Spatie\Searchable\Search;
use Illuminate\Http\Request;

class SearchController extends Controller{  

public function index(Request $request)
  {
    $results = (new Search())
//قسمت هایی که میخوایم جستجو بشن
    -&gt;registerModel(Product::class, [&#039;title&#039;, &#039;body&#039;])
    -&gt;search($request-&gt;input(&#039;query&#039;));
    
    return response()-&gt;json($results);
  }
}خب تا اینجای کار قسمت بکند ما اماده شده و نیاز به ایجاد قسمت فراند داریم که با vue این قسمت میسازیم:&lt;template&gt;     
        &lt;div&gt;       
                &lt;input type=&amp;quottext&amp;quot placeholder=&amp;quotSearch&amp;quot v-model=&amp;quotquery&amp;quot&gt;               
                &lt;ul v-if=&amp;quotresults.length &gt; 0 &amp;&amp; query&amp;quot&gt;        
                         &lt;li v-for=&amp;quotresult in results.slice(0,10)&amp;quot :key=&amp;quotresult.id&amp;quot&gt;           
                                &lt;a :href=&amp;quotresult.url&amp;quot&gt;             
                                        &lt;div v-text=&amp;quotresult.title&amp;quot&gt;&lt;/div&gt;           
                                &lt;/a&gt;         
                        &lt;/li&gt;       
                &lt;/ul&gt;     
        &lt;/div&gt; 
&lt;/template&gt;
    export default {        data() {            return {                query: &amp;quot&amp;quot,                results: []            };        },        watch: {            query(after, before) {                this.searchMembers();            }        },        methods: {            searchMembers() {                axios.get(&#039;products/search&#039;, { params: { query: this.query } })                    .then(response =&gt; this.results = response.data)                    .catch(error =&gt; {});            }        }    }این هم از قسمت ویو جستجو . حالا میتونید این کامپوننت رو هر جای سایتتون که میخواید فراخوانی کنید</description>
                <category>علی علیمحمدی</category>
                <author>علی علیمحمدی</author>
                <pubDate>Sat, 02 May 2020 18:00:06 +0430</pubDate>
            </item>
            </channel>
</rss>