# مشخصات کامل فنی و محصول ## سیستم تیکتینگ مشتریان و کارشناسان فروش **نسخه سند:** 1.0 **نوع پروژه:** Web Application **معماری:** Django Modular Monolith **Backend:** Django + PostgreSQL **Frontend:** Django Templates + HTMX + Alpine.js + Vanilla JavaScript + Tailwind CSS **Deployment:** Docker / Docker Compose / Linux **File Storage:** Local Server Storage **Real-time:** نیاز نیست **Redis:** نیاز نیست در نسخه اول **Celery:** نیاز نیست در نسخه اول **WebSocket:** نیاز نیست **Microservice:** ممنوع/غیرضروری در V1 --- # 1. هدف پروژه هدف، طراحی و پیاده‌سازی یک سیستم تیکتینگ ساده، سریع، امن و قابل توسعه برای ارتباط مشتریان با کارشناسان فروش شرکت است. مشتریان عمدتاً برای مواردی مانند: - استعلام قیمت - درصد تخفیف - شرایط فروش - موجودی - شرایط ارسال - درخواست نمایندگی - مشاوره فروش - سؤالات مربوط به محصولات - شکایات - سایر مسائل فروش با کارشناسان ارتباط برقرار می‌کنند. سیستم نباید تبدیل به یک Messenger دائمی شبیه WhatsApp یا Telegram شود. هر Ticket باید نماینده یک موضوع یا مسئله مشخص باشد. مشتری برای موضوع جدید باید Ticket جدید ایجاد کند. داخل یک Ticket امکان چند مرحله سؤال و جواب وجود دارد، اما فلسفه سیستم بر اساس Ticketing باقی می‌ماند. --- # 2. اهداف اصلی سیستم باید: 1. ورود مشتری با شماره موبایل و OTP را فراهم کند. 2. ثبت‌نام اجباری سنتی نداشته باشد. 3. تعیین نام، نام خانوادگی و Password اختیاری باشد. 4. مشتری بتواند چند Ticket همزمان داشته باشد. 5. مشتری بتواند کارشناس موردنظر خود را انتخاب کند. 6. در صورت عدم انتخاب کارشناس، Ticket وارد صف عمومی کارشناسان شود. 7. کارشناس بتواند Ticket عمومی را برای خودش بردارد. 8. بعد از برداشتن Ticket، اطلاعات آن از سایر کارشناسان مخفی شود. 9. مدیران مجاز همیشه بتوانند همه Ticketها را مشاهده کنند. 10. امکان پاسخ متنی و Attachmentهای مختلف وجود داشته باشد. 11. Voice مستقیماً داخل مرورگر ضبط شود. 12. امکان Edit پیام وجود داشته باشد. 13. تاریخچه Edit کاملاً حفظ شود. 14. حذف پیام کارشناس به صورت Soft Delete باشد. 15. مدیر بتواند متن اصلی پیام حذف‌شده را مشاهده کند. 16. امکان Internal Note وجود داشته باشد. 17. امکان Transfer Ticket وجود داشته باشد. 18. امکان Share کردن Ticket با Expert دیگر وجود داشته باشد. 19. امکان Mention کارشناس وجود داشته باشد. 20. Notification مشتری از طریق SMS انجام شود. 21. Notification کارشناسان از طریق Dashboard و Bale انجام شود. 22. مشتری از طریق Magic Link بتواند سریع Ticket را باز کند. 23. Permission System منعطف باشد. 24. Priority و SLA قابل استفاده باشند. 25. فایل‌ها روی Storage محلی Server ذخیره شوند. --- # 3. محدوده کاربران حداکثر بار مورد انتظار در نسخه فعلی: - حدود 200 مشتری - حداکثر 20 کارشناس فروش - یک شرکت - بدون Multi-Tenancy - حجم پیام و Ticket نسبتاً پایین در نتیجه نیازی به معماری پیچیده Distributed وجود ندارد. --- # 4. معماری کلان معماری سیستم باید: **Modular Monolith** باشد. تمام Business Logic داخل یک Django Project قرار می‌گیرد ولی Domainها به Appهای منطقی جدا تقسیم می‌شوند. ساختار پیشنهادی: ```text project/ │ ├── config/ │ ├── settings/ │ ├── urls.py │ ├── asgi.py │ └── wsgi.py │ ├── apps/ │ │ │ ├── accounts/ │ ├── customers/ │ ├── staff/ │ ├── tickets/ │ ├── messaging/ │ ├── attachments/ │ ├── notifications/ │ ├── integrations/ │ │ ├── sms/ │ │ └── bale/ │ │ │ └── core/ │ ├── templates/ ├── static/ ├── media/ ├── tests/ │ ├── docker/ ├── docker-compose.yml ├── Dockerfile └── manage.py ``` --- # 5. معماری لایه‌ای داخل Appها Business Logic نباید مستقیماً داخل Viewها نوشته شود. ساختار پیشنهادی برای Appهایی مثل tickets: ```text tickets/ ├── models.py ├── forms.py ├── urls.py ├── selectors.py ├── policies.py ├── services/ │ ├── create_ticket.py │ ├── claim_ticket.py │ ├── transfer_ticket.py │ ├── ticket_access.py │ └── status.py ├── views/ ├── admin.py └── tests/ ``` ### Responsibility `views` فقط: - HTTP - Forms - Redirect - HTMX response را مدیریت کند. `services` Business Logic را اجرا کند. `selectors` Queryهای خواندن اطلاعات را مدیریت کند. `policies` Permission و authorization را مدیریت کند. --- # 6. اصل بسیار مهم Authorization هیچ Permission مهمی نباید فقط در: - Template - JavaScript - HTMX - CSS اعمال شود. تمام Permissionها باید Server Side کنترل شوند. مخفی‌کردن یک Button در UI به معنی جلوگیری از عملیات نیست. --- # 7. User Model از ابتدای پروژه باید Custom User Model ایجاد شود. بعداً تغییر `AUTH_USER_MODEL` بسیار هزینه‌بر است. مدل مفهومی: ```text User --------- id public_id username mobile email password account_type is_active is_staff created_at updated_at last_login ``` `account_type`: ```text CUSTOMER STAFF ``` کاربر Customer می‌تواند Password نداشته باشد. در این حالت: ```python user.set_unusable_password() ``` استفاده شود. --- # 8. CustomerProfile ```text CustomerProfile ---------------- user first_name last_name customer_type created_at updated_at ``` `customer_type` اختیاری است. مقادیر اولیه: ```text PERSON CONSUMER SHOP REPRESENTATIVE COMPANY OTHER ``` نمایش فارسی: ```text شخص مصرف‌کننده نهایی فروشگاه نمایندگی شرکت سایر ``` --- # 9. StaffProfile ```text StaffProfile ------------- user display_name avatar mobile bale_chat_id bale_notifications_enabled is_customer_selectable sort_order is_active created_at updated_at ``` فقط کارشناسانی که: ```text is_customer_selectable = True ``` دارند در صفحه انتخاب کارشناس به مشتری نمایش داده شوند. --- # 10. احراز هویت مشتری روش اصلی: ```text Mobile ↓ OTP ↓ Login ``` Password اجباری نیست. پس از ورود موفق، سیستم می‌تواند پیشنهاد دهد: > برای ورود آسان‌تر در آینده می‌توانید رمز عبور تعیین کنید. ولی این درخواست نباید Blocking باشد. --- # 11. ورود با Password در صورت تعیین Password، مشتری می‌تواند در آینده با: ```text mobile + password ``` هم وارد شود. با این حال روش OTP همچنان باید در صفحه Login در اولویت UX باشد. --- # 12. Staff Login کارشناسان و مدیران صفحه Login مستقل داشته باشند. مثلاً: ```text /staff/login/ ``` Customer login: ```text /login/ ``` Staff Login با Username/Password انجام شود. --- # 13. OTP Architecture سیستم SMS نباید وابسته به Provider خاص باشد. از Adapter Pattern و Protocol استفاده شود. Interface مفهومی: ```python class SMSProvider(Protocol): def send_otp( self, mobile: str, code: str, ) -> SMSResult: ... def send_message( self, mobile: str, message: str, ) -> SMSResult: ... ``` Providerهای آینده می‌توانند شامل: ```text KavenegarProvider SMSIrProvider IPPanelProvider FarazSMSProvider CustomSMSProvider ``` باشند. Business Logic حق ندارد مستقیم API Provider را Call کند. --- # 14. OTPChallenge Model ```text OTPChallenge ------------- id mobile purpose code_hash expires_at attempt_count max_attempts verified_at created_at request_ip user_agent ``` Raw OTP نباید داخل Database ذخیره شود. --- # 15. تنظیمات OTP مقادیر اولیه پیشنهادی: ```text OTP Length: 6 digits Expire: 2 minutes Resend cooldown: 60 seconds Max verification attempts: 5 ``` تمام موارد باید از Settings قابل تغییر باشند. Rate Limiting نیز باید برای: - mobile - IP اعمال شود. Redis برای این حجم کاربر لازم نیست. Rate Limit می‌تواند با PostgreSQL انجام شود. --- # 16. شماره موبایل شماره‌ها باید قبل از ذخیره Normalize شوند. برای مثال: ```text 09121234567 ``` به شکل Canonical: ```text +989121234567 ``` ذخیره شود. تمام جستجو و Unique Constraint بر اساس شماره Normalize‌شده باشد. --- # 17. TicketCategory Category در Database ذخیره شود. ```text TicketCategory --------------- id title slug description icon sort_order is_active created_at updated_at ``` مثال: ```text استعلام قیمت درصد تخفیف شرایط فروش موجودی شرایط ارسال درخواست نمایندگی شکایت مشاوره سایر ``` انتخاب Category برای Customer اجباری است. Category غیرفعال برای Ticketهای قدیمی باقی بماند ولی در Ticket جدید نمایش داده نشود. --- # 18. Ticket Model مدل اصلی: ```text Ticket ---------------------- id public_id ticket_number customer category subject status priority selected_expert owner origin_visibility created_at updated_at last_activity_at first_response_at last_customer_message_at last_staff_message_at first_response_due_at resolution_due_at resolved_at closed_at reopened_at ``` --- # 19. Ticket Number Ticket باید شماره قابل خواندن داشته باشد. مثلاً: ```text TK-1405-001254 ``` اما Permission و URL نباید بر اساس این شماره امنیت ایجاد کنند. URLها از `public_id` یا UUID استفاده کنند. مثلاً: ```text /tickets/9d7b.../ ``` --- # 20. Subject Subject اجباری است. محدوده پیشنهادی: ```text 5 تا 200 character ``` نمونه: > استعلام قیمت لوله 110 --- # 21. اولین پیام Ticket Ticket علاوه بر Subject باید یک Initial Message داشته باشد. Initial Message می‌تواند شامل: - Text - Attachment - Text + Attachments باشد. UX باید مشتری را تشویق کند مسئله را کامل توضیح دهد. --- # 22. Ticket Status Statusهای اصلی: ```text NEW OPEN WAITING_CUSTOMER WAITING_STAFF RESOLVED CLOSED REOPENED ``` --- # 23. معنی Statusها ### NEW Ticket ایجاد شده ولی هنوز رسیدگی واقعی شروع نشده است. ### OPEN کارشناس Ticket را برداشته یا رسیدگی به آن را شروع کرده است. ### WAITING_CUSTOMER کارشناس پاسخ داده و اکنون انتظار پاسخ مشتری وجود دارد. ### WAITING_STAFF مشتری پاسخ داده و سیستم منتظر پاسخ کارشناس است. ### RESOLVED کارشناس مسئله را حل‌شده اعلام کرده است. ### CLOSED Ticket بسته شده است. ### REOPENED Ticket بسته یا Resolve شده بوده و Customer دوباره پیام داده است. --- # 24. Reopen اگر Ticket: ```text RESOLVED ``` یا: ```text CLOSED ``` باشد و Customer پیام جدید ارسال کند: Ticket خودکار به: ```text REOPENED ``` تغییر وضعیت دهد. این عملیات باید Transactional باشد. --- # 25. Priority Priorityهای اولیه: ```text NORMAL IMPORTANT URGENT ``` Default: ```text NORMAL ``` Customer اجازه تغییر Priority ندارد. کارشناس یا کاربران دارای Permission می‌توانند Priority را تغییر دهند. --- # 26. منطق انتخاب Expert توسط Customer سه حالت مهم وجود دارد. ## حالت اول: Customer Expert انتخاب می‌کند مثلاً Customer: ```text احمد ``` را انتخاب می‌کند. در این حالت: ```text selected_expert = Ahmad owner = NULL ``` و فقط: - Ahmad - مدیران دارای دسترسی کامل می‌توانند Ticket را مشاهده کنند. سایر کارشناسان نباید حتی محتوای Ticket را ببینند. این Ticket وارد Public Queue نمی‌شود. --- # 27. عدم انتخاب Expert اگر Customer کارشناسی انتخاب نکند: ```text selected_expert = NULL owner = NULL ``` Ticket وارد Public Queue می‌شود. تمام کارشناسان مجاز می‌توانند: - عنوان - محتوا - Attachment - Customer - سابقه مجاز را مشاهده کنند. --- # 28. Claim / Lock Ticket در Public Queue هر Expert می‌تواند: > برداشتن تیکت یا: > اختصاص به من را انتخاب کند. پس: ```text owner = current_user ``` خواهد شد. --- # 29. Race Condition در Claim ممکن است دو Expert همزمان روی: > اختصاص به من کلیک کنند. Claim باید با: ```python transaction.atomic() ``` و: ```python select_for_update() ``` پیاده‌سازی شود. فقط اولین Expert موفق شود. Expert دوم پیام مناسب دریافت کند: > این تیکت لحظاتی قبل توسط «احمد رضایی» برداشته شد. --- # 30. رفتار Ticket بعد از Claim بعد از Claim: فقط: - Owner - کارشناسان Shared - مدیران دارای Permission حق مشاهده محتوای Ticket را دارند. سایر کارشناسان فقط اطلاعات محدود زیر را ببینند: ```text Ticket Title Owner Locked/Assigned indicator ``` و نباید قادر باشند: - Message را بخوانند - Attachment ببینند - Customer information ببینند - Reply کنند - Internal Note ببینند --- # 31. Ticket انتخاب‌شده توسط Customer یک تفاوت مهم: اگر Customer از ابتدا Expert خاصی انتخاب کرده باشد، سایر کارشناسان حتی Metadata آن را در Public Queue نبینند. این Ticket خصوصی محسوب می‌شود. --- # 32. TicketAccessGrant برای دادن دسترسی به Expertهای دیگر مدل جداگانه ایجاد شود. ```text TicketAccessGrant ------------------ id ticket user granted_by permission_level created_at revoked_at ``` در V1 می‌توان `permission_level` را ساده نگه داشت: ```text FULL ``` ولی فیلد از ابتدا وجود داشته باشد تا بعداً: ```text VIEW_ONLY REPLY FULL ``` قابل توسعه باشد. --- # 33. Share Ticket Owner یا Manager دارای Permission بتواند Expert دیگری را به Ticket اضافه کند. عملیات UI: > اشتراک با کارشناس پس از Share، Expert موردنظر: - محتوای Ticket - پیام‌ها - فایل‌ها را مشاهده می‌کند و در صورت FULL بودن دسترسی پاسخ می‌دهد. --- # 34. Revoke Access Owner یا مدیر مجاز بتواند Access یک Expert را حذف کند. Manager دارای Full Access هرگز توسط Ticket Grant محدود نمی‌شود. --- # 35. Transfer Ticket Owner بتواند Ticket را بدون نیاز به تأیید مدیر به Expert دیگری Transfer کند. مثلاً: ```text Ahmad → Reza ``` پس: ```text owner = Reza ``` شود. Transfer باید Transactional باشد. --- # 36. دسترسی Owner قبلی بعد از Transfer Default Behavior: پس از Transfer، Owner قبلی دسترسی اختصاصی خود را از دست می‌دهد. در صورت نیاز باید به عنوان Shared Expert به Ticket اضافه شود. این رفتار از نشت ناخواسته اطلاعات جلوگیری می‌کند. --- # 37. TicketEvent Audit Log عمومی در V1 لازم نیست. اما Domain History لازم است. مدل: ```text TicketEvent ------------ id ticket event_type actor metadata created_at ``` Eventها: ```text CREATED CLAIMED TRANSFERRED SHARED ACCESS_REVOKED STATUS_CHANGED PRIORITY_CHANGED REOPENED RESOLVED CLOSED ``` این مدل برای: - Timeline - SLA - Debug - تاریخچه Transfer استفاده می‌شود. این سیستم جای Audit Log جامع را نمی‌گیرد. --- # 38. Permissions Role names نباید داخل Business Logic Hard-Code شوند. یعنی کد نباید بگوید: ```python if user.role == "manager": ``` بلکه: ```python user.has_perm(...) ``` یا Ticket Policy Service استفاده شود. --- # 39. Django Groups برای ساده نگه‌داشتن معماری از: - Django Groups - Django Permissions استفاده شود. مثلاً Groupهای اولیه: ```text Sales Expert Sales Manager Administrator ``` نام Group Business Logic نیست. Permission تعیین‌کننده رفتار است. --- # 40. Permissions پیشنهادی ```text tickets.view_all_tickets tickets.view_private_ticket tickets.claim_ticket tickets.transfer_ticket tickets.share_ticket tickets.change_ticket_status tickets.change_ticket_priority tickets.view_customer_history messaging.add_internal_note messaging.edit_own_message messaging.delete_own_message messaging.view_deleted_message_content messaging.view_message_revisions attachments.download_attachment staff.manage_staff tickets.manage_categories attachments.manage_attachment_policy ``` --- # 41. مدیران مدیرانی که: ```text tickets.view_all_tickets ``` دارند باید بدون استثناء تمام Ticketها را مشاهده کنند. فرقی نمی‌کند: - Customer Expert انتخاب کرده باشد. - Ticket Locked شده باشد. - Ticket Shared نشده باشد. - Ticket متعلق به Expert دیگری باشد. --- # 42. TicketPolicyService Permission Ticket باید در یک نقطه مرکزی باشد. مثلاً: ```python TicketPolicy.can_view(user, ticket) TicketPolicy.can_reply(user, ticket) TicketPolicy.can_claim(user, ticket) TicketPolicy.can_transfer(user, ticket) TicketPolicy.can_share(user, ticket) TicketPolicy.can_view_deleted_content(user, ticket) ``` هیچ View نباید منطق Permission را دوباره بنویسد. --- # 43. Message Model ```text Message --------------- id public_id ticket author body message_type is_internal created_at updated_at edited_at deleted_at deleted_by ``` --- # 44. انواع Message ```text PUBLIC INTERNAL_NOTE ``` Customer فقط PUBLIC را مشاهده می‌کند. --- # 45. Internal Note Expert بتواند Note داخلی ایجاد کند. مثال: > این مشتری تخفیف ویژه درخواست کرده، قبل از اعلام قیمت با مدیر هماهنگ شود. Internal Note: - در Timeline کارشناسان نمایش داده شود. - از پیام‌های معمولی کاملاً متمایز باشد. - Customer هرگز آن را نبیند. - در SMS Customer اثر نداشته باشد. --- # 46. Edit Message Customer و Expert بتوانند Message خودشان را Edit کنند. بعد از Edit: ```text edited_at ``` ثبت شود. در UI: > ویرایش شده نمایش داده شود. --- # 47. MessageRevision قبل از تغییر متن، نسخه قبلی ذخیره شود. ```text MessageRevision ---------------- id message body edited_by created_at ``` مثلاً: نسخه 1: ```text قیمت 1,200,000 تومان ``` نسخه جدید: ```text قیمت 1,250,000 تومان ``` هر دو باید قابل بازیابی برای Manager مجاز باشند. --- # 48. مشاهده Revision Customer الزاماً Revision History را نمی‌بیند. Manager یا فرد دارای: ```text messaging.view_message_revisions ``` می‌تواند تاریخچه را مشاهده کند. --- # 49. Delete Message Expert می‌تواند Message خودش را Delete کند. اما: **هیچ Hard Delete انجام نشود.** ممنوع: ```python message.delete() ``` به جای آن: ```text deleted_at deleted_by ``` ثبت شود. Body اصلی در Database باقی بماند. --- # 50. نمایش پیام حذف‌شده Customer و کارشناسان عادی ببینند: > این پیام توسط فرستنده حذف شده است. Manager دارای Permission بتواند: - Original Body - Attachments - Author - Delete time را مشاهده کند. --- # 51. Attachments روی Message حذف‌شده Attachmentهای Message حذف‌شده نباید از Disk حذف شوند. مدیر باید همچنان بتواند آن‌ها را مشاهده کند. --- # 52. MessageMention Mention نباید فقط Regex روی متن باشد. مدل: ```text MessageMention --------------- message mentioned_user created_at ``` در Frontend Autocomplete Expert نمایش داده شود. --- # 53. Mention و Access Mention به‌تنهایی نباید Permission امنیتی را دور بزند. اگر Expert Mention‌شده به Ticket دسترسی ندارد: سیستم باید ابتدا Access مناسب ایجاد کند یا UI به فرستنده اعلام کند: > برای Mention کردن این کارشناس ابتدا باید دسترسی تیکت به او داده شود. Owner و Manager می‌توانند: > Share + Mention را در یک Flow انجام دهند. --- # 54. Bale Notification برای Mention پس از Mention، Expert از طریق Bale Notification دریافت کند. مثال: ```text تیکت TK-1405-1254 علی رضایی شما را Mention کرد: «لطفاً شرایط تخفیف این مشتری را بررسی کنید.» مشاهده تیکت: https://support.example.com/... ``` --- # 55. Attachment Model ```text Attachment ---------------- id public_id message original_name stored_name relative_path attachment_type mime_type extension file_size uploaded_by created_at ``` --- # 56. Attachment Types سیستم باید حداقل پشتیبانی کند: ```text IMAGE VIDEO VOICE PDF WORD EXCEL ZIP RAR OTHER ``` --- # 57. AttachmentPolicy محدودیت‌ها نباید Hard-Code باشند. مدل: ```text AttachmentPolicy ----------------- attachment_type is_enabled max_file_size max_files_per_message allowed_extensions allowed_mime_types sort_order updated_at ``` --- # 58. محدودیت اولیه پیشنهادی این اعداد فقط Default هستند و باید قابل تغییر باشند. ```text IMAGE 10 MB VOICE 25 MB VIDEO 100 MB PDF 30 MB WORD 30 MB EXCEL 30 MB ZIP 50 MB RAR 50 MB ``` --- # 59. Multiple Attachments هر Message باید بتواند چند Attachment داشته باشد. مثلاً: ```text متن + 4 تصویر + یک PDF + یک Voice ``` مجاز باشد. --- # 60. Attachment Composer UI قبل از ارسال، UI باید نشان دهد: - Thumbnail تصویر - نام فایل - Size - Type - Progress - حذف قبل از ارسال - Error فایل نامعتبر --- # 61. File Validation صرفاً Extension کافی نیست. Server باید بررسی کند: 1. Extension 2. MIME Type 3. File Signature برای File Signature استفاده از `libmagic` یا ابزار مشابه توصیه می‌شود. --- # 62. ZIP و RAR ZIP/RAR نباید روی Server Extract شوند. فقط به عنوان Attachment ذخیره شوند. این موضوع برای جلوگیری از: - path traversal - zip bombs - فایل‌های اجرایی داخلی مهم است. --- # 63. File Name Security نام واقعی فایل کاربر نباید مستقیماً نام فایل Storage باشد. مثلاً: ```text invoice.pdf ``` روی Disk به: ```text 1b3ef72c-....pdf ``` تبدیل شود. `original_name` فقط Metadata است. --- # 64. Media Storage Storage کاملاً Local Server است. ساختار پیشنهادی: ```text /media/ └── tickets/ └── 2026/ └── 09/ └── / └── / ├── file_uuid.jpg ├── file_uuid.pdf └── file_uuid.webm ``` --- # 65. Private Media فایل‌های Ticket نباید مستقیماً از Public `/media/` قابل دسترسی باشند. این URL ناامن است: ```text https://site.com/media/tickets/xxx.pdf ``` چون Permission را دور می‌زند. Download باید ابتدا Django Authorization را بررسی کند. --- # 66. Nginx X-Accel-Redirect روش پیشنهادی: 1. درخواست Download وارد Django می‌شود. 2. Django Permission را بررسی می‌کند. 3. در صورت مجاز بودن Header مخصوص Nginx برگردانده می‌شود. 4. Nginx فایل را Efficient ارسال می‌کند. Location فایل‌ها در Nginx باید: ```text internal ``` باشد. --- # 67. Voice Recording Voice با: ```text MediaDevices.getUserMedia() MediaRecorder ``` پیاده‌سازی شود. نیازی به Plugin خارجی نیست. --- # 68. Voice UI Voice Recorder شامل: ```text Record Stop Pause Resume Timer Preview Delete Re-record Send ``` باشد. حداقل امکانات: ```text 🎤 ضبط ⏱ مدت ضبط ▶ پخش 🗑 حذف ➤ ارسال ``` --- # 69. Browser Compatibility Voice Format ضبط ممکن است بین Browserها متفاوت باشد. مثلاً: ```text audio/webm audio/ogg audio/mp4 ``` Frontend باید با: ```javascript MediaRecorder.isTypeSupported() ``` Format مناسب را انتخاب کند. Server نباید فقط یک MIME Type را قبول کند. --- # 70. Video در V1 نیازی به Transcoding Video نیست. Video همان Format مجاز Upload می‌شود. در Browser در صورت پشتیبانی: ```html