Переглянути джерело

feat: add version field to places (track edits), display dates and edit count in PlaceCard

neyrogovnarik 1 місяць тому
батько
коміт
4791687f6e

+ 1 - 0
backend/internal/models/place.go

@@ -26,6 +26,7 @@ type Place struct {
 	Features  []Feature  `json:"features"`
 	Images    []PlaceImage `json:"images,omitempty"`
 	Owner     *User      `json:"owner,omitempty"`
+	Version   int        `json:"version"`
 	CreatedAt time.Time  `json:"created_at"`
 	UpdatedAt time.Time  `json:"updated_at"`
 	PublishedAt *time.Time `json:"published_at,omitempty"`

+ 7 - 5
backend/internal/repository/places.go

@@ -55,7 +55,7 @@ func (r *PlaceRepo) GetByID(ctx context.Context, id string) (*models.Place, erro
 		        ST_Y(coordinates::geometry), ST_X(coordinates::geometry),
 		        cover_image, access_info, status, moderation_comment,
 		        rating, reviews_count, hourly_rate, currency, min_hours, booking_url,
-		        created_at, updated_at, published_at, deleted_at
+		        version, created_at, updated_at, published_at, deleted_at
 		 FROM places WHERE id = $1 AND deleted_at IS NULL`, id)
 	return scanPlace(row)
 }
@@ -66,7 +66,7 @@ func (r *PlaceRepo) GetByIDWithDetails(ctx context.Context, id string) (*models.
 		       ST_Y(p.coordinates::geometry), ST_X(p.coordinates::geometry),
 		       p.cover_image, p.access_info, p.status, p.moderation_comment,
 		       p.rating, p.reviews_count, p.hourly_rate, p.currency, p.min_hours, p.booking_url,
-		       p.created_at, p.updated_at, p.published_at, p.deleted_at,
+		       p.version, p.created_at, p.updated_at, p.published_at, p.deleted_at,
 	       COALESCE(json_agg(DISTINCT jsonb_build_object('id', t.id, 'name', t.name, 'category', t.category))
 	                FILTER (WHERE t.id IS NOT NULL), '[]'::json) as tags,
 	       COALESCE(json_agg(DISTINCT jsonb_build_object('id', f.id, 'name', f.name, 'category', f.category, 'icon', f.icon))
@@ -88,6 +88,7 @@ func (r *PlaceRepo) GetByIDWithDetails(ctx context.Context, id string) (*models.
 		&p.Lat, &p.Lng,
 		&p.CoverImage, &p.AccessInfo, &p.Status, &p.ModerationComment,
 		&p.Rating, &p.ReviewsCount, &p.HourlyRate, &p.Currency, &p.MinHours, &p.BookingURL,
+		&p.Version,
 		&p.CreatedAt, &p.UpdatedAt, &p.PublishedAt, &p.DeletedAt,
 		&tagsJSON, &featuresJSON,
 	)
@@ -113,7 +114,7 @@ func (r *PlaceRepo) List(ctx context.Context, filter models.PlaceFilter) ([]*mod
 	             ST_Y(p.coordinates::geometry), ST_X(p.coordinates::geometry),
 	             p.cover_image, p.access_info, p.status, p.moderation_comment,
 	             p.rating, p.reviews_count, p.hourly_rate, p.currency, p.min_hours, p.booking_url,
-	             p.created_at, p.updated_at, p.published_at, p.deleted_at
+	             p.version, p.created_at, p.updated_at, p.published_at, p.deleted_at
 	      FROM places p`
 	args := pgx.NamedArgs{}
 	joins := ""
@@ -197,7 +198,7 @@ func (r *PlaceRepo) Update(ctx context.Context, p *models.Place) error {
 		`UPDATE places SET title=$1, description=$2, address=$3,
 		 coordinates=ST_SetSRID(ST_MakePoint($4, $5), 4326),
 		 cover_image=$6, access_info=$7, status=$8, hourly_rate=$9, currency=$10, min_hours=$11,
-		 booking_url=$12, updated_at=now()
+		 booking_url=$12, updated_at=now(), version=version+1
 		 WHERE id=$13 AND deleted_at IS NULL`,
 		p.Title, p.Description, p.Address,
 		p.Lng, p.Lat,
@@ -240,7 +241,7 @@ func (r *PlaceRepo) GetByIDRaw(ctx context.Context, id string) (*models.Place, e
 		        ST_Y(coordinates::geometry), ST_X(coordinates::geometry),
 		        cover_image, access_info, status, moderation_comment,
 		        rating, reviews_count, hourly_rate, currency, min_hours, booking_url,
-		        created_at, updated_at, published_at, deleted_at
+		        version, created_at, updated_at, published_at, deleted_at
 		 FROM places WHERE id = $1`, id)
 	return scanPlace(row)
 }
@@ -412,6 +413,7 @@ func scanPlace(row interface{ Scan(dest ...any) error }) (*models.Place, error)
 		&p.Lat, &p.Lng,
 		&p.CoverImage, &p.AccessInfo, &p.Status, &p.ModerationComment,
 		&p.Rating, &p.ReviewsCount, &p.HourlyRate, &p.Currency, &p.MinHours, &p.BookingURL,
+		&p.Version,
 		&p.CreatedAt, &p.UpdatedAt, &p.PublishedAt, &p.DeletedAt,
 	)
 	if err != nil {

+ 1 - 0
backend/migrations/000013_add_place_version.down.sql

@@ -0,0 +1 @@
+ALTER TABLE places DROP COLUMN version;

+ 1 - 0
backend/migrations/000013_add_place_version.up.sql

@@ -0,0 +1 @@
+ALTER TABLE places ADD COLUMN version INT NOT NULL DEFAULT 0;

+ 9 - 0
frontend/src/app/places/my/page.tsx

@@ -54,6 +54,10 @@ const FILTERS: { key: string; label: string }[] = [
   { key: 'rejected', label: 'Отклонено' },
 ]
 
+function fmtDate(d: string) {
+  return new Date(d).toLocaleDateString('ru-RU', { day: 'numeric', month: 'short', year: 'numeric', hour: '2-digit', minute: '2-digit' })
+}
+
 function PlaceCard({ place, onEdit, onDeleted }: { place: Place; onEdit: () => void; onDeleted: () => void }) {
   const [showConfirm, setShowConfirm] = useState(false)
   const [deleting, setDeleting] = useState(false)
@@ -69,6 +73,11 @@ function PlaceCard({ place, onEdit, onDeleted }: { place: Place; onEdit: () => v
             <span className={st.color}>{st.label}</span>
             {place.type === 'studio' && <span className="text-white/40">Студия</span>}
           </div>
+          <div className="mt-1.5 flex flex-wrap gap-x-4 gap-y-1 text-xs text-white/40">
+            <span>Создано {fmtDate(place.created_at)}</span>
+            <span>Изменено {fmtDate(place.updated_at)}</span>
+            <span>Правок: {place.version}</span>
+          </div>
           {place.moderation_comment && (
             <p className="mt-2 rounded bg-yellow-500/10 px-3 py-2 text-sm text-yellow-400">
               Комментарий модератора: {place.moderation_comment}

+ 1 - 0
frontend/src/types/index.ts

@@ -98,6 +98,7 @@ export interface Place {
   currency?: string
   min_hours?: number
   booking_url?: string
+  version: number
   created_at: string
   updated_at: string
 }