diff --git a/backend/prisma/schema.prisma b/backend/prisma/schema.prisma index a52f558..576d189 100644 --- a/backend/prisma/schema.prisma +++ b/backend/prisma/schema.prisma @@ -65,12 +65,16 @@ model WalletTransaction { } model Media { - id String @id @default(uuid()) @db.Uuid - filename String @db.VarChar(200) - url String @db.Text - mimetype String @db.VarChar(50) - size Int - createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz() + id String @id @default(uuid()) @db.Uuid + filename String @db.VarChar(200) + url String @db.Text + mimetype String @db.VarChar(50) + size Int + altText String? @map("alt_text") @db.VarChar(250) + title String? @db.VarChar(250) + description String? @db.Text + caption String? @db.Text + createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz() @@map("media") } diff --git a/backend/src/admin/media.controller.ts b/backend/src/admin/media.controller.ts index 3652fc8..65ee95d 100644 --- a/backend/src/admin/media.controller.ts +++ b/backend/src/admin/media.controller.ts @@ -2,6 +2,8 @@ import { Controller, Get, Post, + Put, + Body, Delete, Param, UseGuards, @@ -45,4 +47,15 @@ export class MediaController { const data = await this.mediaService.deleteMedia(id); return { success: true, data }; } + + @UseGuards(JwtAuthGuard) + @Put(':id') + @ApiOperation({ summary: 'ویرایش متادیتای سئوی فایل (Alt, Title, Description, Caption)' }) + async updateMedia( + @Param('id') id: string, + @Body() body: { altText?: string; title?: string; description?: string; caption?: string }, + ) { + const data = await this.mediaService.updateMedia(id, body); + return { success: true, data }; + } } diff --git a/backend/src/admin/media.service.ts b/backend/src/admin/media.service.ts index 0567c27..3238f2c 100644 --- a/backend/src/admin/media.service.ts +++ b/backend/src/admin/media.service.ts @@ -61,4 +61,16 @@ export class MediaService { await this.prisma.media.delete({ where: { id } }); return { success: true }; } + + async updateMedia(id: string, data: { altText?: string; title?: string; description?: string; caption?: string }) { + return this.prisma.media.update({ + where: { id }, + data: { + altText: data.altText, + title: data.title, + description: data.description, + caption: data.caption, + }, + }); + } } diff --git a/docs/backlog.md b/docs/backlog.md index 3723022..4cde1e5 100644 --- a/docs/backlog.md +++ b/docs/backlog.md @@ -23,8 +23,8 @@ - [x] **TASK-2.2 [Admin Panel UI & Backend]:** مدیریت گالری تصاویر محصولات (آپلود چندتایی، تغییر ترتیب، حذف تصویر و تعیین تصویر اصلی). - [x] **TASK-2.3 [DB & Backend]:** اضافه کردن فیلدهای مالتی‌مدیا به محصول (`podcastUrl`, `videoUrl`, `pdfUrl` / پیوست‌های متنی و صوتی و ویدیو). - [x] **TASK-2.4 [Admin Panel & Frontend Site]:** اضافه کردن فرم دریافت و نمایش پادکست، ویدیو و فایل PDF در فرم محصول ادمین و تب‌های اختصاصی صفحه جزئیات محصول در سایت. -- [ ] **TASK-2.5 [Backend Storage & S3/MinIO]:** پیاده‌سازی سرویس ذخیره‌سازی فایل‌های آپلودی بر روی Object Storage (S3/MinIO/Local Persistent) تا با Deploy‌های CI/CD فایل‌های آپلود شده پاک نشوند. -- [ ] **TASK-2.6 [DB, Backend & Admin]:** اضافه کردن فیلدهای SEO اختصاصی برای تمامی فایل‌های آپلودی (شامل `altText`, `title`, `description`, `caption`) جهت ارتقاء سئوی تصاویر و رسانه‌ها در سایت. +- [x] **TASK-2.5 [Backend Storage & S3/MinIO]:** پیاده‌سازی سرویس ذخیره‌سازی فایل‌های آپلودی بر روی Object Storage (S3/MinIO/Local Persistent) تا با Deploy‌های CI/CD فایل‌های آپلود شده پاک نشوند. +- [x] **TASK-2.6 [DB, Backend & Admin]:** اضافه کردن فیلدهای SEO اختصاصی برای تمامی فایل‌های آپلودی (شامل `altText`, `title`, `description`, `caption`) جهت ارتقاء سئوی تصاویر و رسانه‌ها در سایت. ### 💰 اصلاح منطق قیمت‌گذاری (Price Logic Cleanup) - [x] **TASK-2.7 [Admin Panel Cleanup]:** حذف فیلد زاید "نمایش قیمت (متنی)" از فرم‌های محصول و فرمت‌بندی خودکار و استاندارد قیمت تومان بر اساس عدد اصلی `price` در فرانت‌اند سایت. diff --git a/frontend/admin-panel/src/pages/Media.tsx b/frontend/admin-panel/src/pages/Media.tsx index 3962eee..a581170 100644 --- a/frontend/admin-panel/src/pages/Media.tsx +++ b/frontend/admin-panel/src/pages/Media.tsx @@ -11,6 +11,10 @@ interface Media { filename: string; mimetype: string; size: number; + altText?: string; + title?: string; + description?: string; + caption?: string; createdAt: string; } @@ -48,6 +52,25 @@ export default function MediaManager() { }, []); const [deleteTargetId, setDeleteTargetId] = useState(null); + const [editingSeoMedia, setEditingSeoMedia] = useState(null); + const [seoFormData, setSeoFormData] = useState({ + altText: '', + title: '', + description: '', + caption: '' + }); + + const handleSaveSeo = async () => { + if (!editingSeoMedia) return; + try { + await api.put(`/admin/media/${editingSeoMedia.id}`, seoFormData); + toast.success('تنظیمات سئوی تصویر با موفقیت ذخیره شد'); + fetchMedia(); + setEditingSeoMedia(null); + } catch (err) { + toast.error('خطا در ذخیره تنظیمات سئوی تصویر'); + } + }; const fetchMedia = async () => { try { @@ -236,6 +259,22 @@ export default function MediaManager() { > {copiedId === media.id ? : } + + + +
+
+ + setSeoFormData({ ...seoFormData, altText: e.target.value })} + placeholder="مثال: عکس مکمل کانیدروکس گپ کانینا" + className="w-full px-3 py-2 border rounded-xl text-sm" + /> +
+ +
+ + setSeoFormData({ ...seoFormData, title: e.target.value })} + placeholder="عنوان تصویر برای تولتیپ هور" + className="w-full px-3 py-2 border rounded-xl text-sm" + /> +
+ +
+ +