diff --git a/src/common/constants/business-highlights.ts b/src/common/constants/business-highlights.ts new file mode 100644 index 0000000..e7e59bd --- /dev/null +++ b/src/common/constants/business-highlights.ts @@ -0,0 +1,47 @@ +/** Shared catalog of landing hero highlights (platform-wide, not per-category). */ + +export const BUSINESS_HIGHLIGHT_KEYS = [ + 'customer_satisfaction', + 'professional_staff', + 'online_booking', + 'fair_pricing', + 'clean_space', + 'modern_equipment', + 'quality_materials', + 'fast_service', + 'experienced_team', + 'easy_access', + 'secure_payment', + 'friendly_support', +] as const; + +export type BusinessHighlightKey = (typeof BUSINESS_HIGHLIGHT_KEYS)[number]; + +export const DEFAULT_BUSINESS_HIGHLIGHT_KEYS: BusinessHighlightKey[] = [ + 'customer_satisfaction', + 'professional_staff', + 'online_booking', +]; + +const KEY_SET = new Set(BUSINESS_HIGHLIGHT_KEYS); + +export function isBusinessHighlightKey(value: string): value is BusinessHighlightKey { + return KEY_SET.has(value); +} + +/** Keep at most 3 unique valid keys; empty/invalid input → empty array (caller may fall back to defaults). */ +export function sanitizeBusinessHighlightKeys( + keys: string[] | null | undefined, +): BusinessHighlightKey[] { + if (!keys?.length) return []; + const seen = new Set(); + const result: BusinessHighlightKey[] = []; + for (const raw of keys) { + const key = String(raw).trim(); + if (!isBusinessHighlightKey(key) || seen.has(key)) continue; + seen.add(key); + result.push(key); + if (result.length >= 3) break; + } + return result; +} diff --git a/src/database/migrations/1789047600000-AddBusinessHeroHighlightKeys.ts b/src/database/migrations/1789047600000-AddBusinessHeroHighlightKeys.ts new file mode 100644 index 0000000..1fd14a6 --- /dev/null +++ b/src/database/migrations/1789047600000-AddBusinessHeroHighlightKeys.ts @@ -0,0 +1,19 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddBusinessHeroHighlightKeys1789047600000 implements MigrationInterface { + name = 'AddBusinessHeroHighlightKeys1789047600000'; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + ALTER TABLE "businesses" + ADD COLUMN IF NOT EXISTS "hero_highlight_keys" jsonb + `); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + ALTER TABLE "businesses" + DROP COLUMN IF EXISTS "hero_highlight_keys" + `); + } +} diff --git a/src/entities/business.entity.ts b/src/entities/business.entity.ts index a4fdacb..3ce0c1c 100644 --- a/src/entities/business.entity.ts +++ b/src/entities/business.entity.ts @@ -85,6 +85,10 @@ export class BusinessEntity { @Column({ type: 'varchar', length: 20, default: 'first_booking' }) promoDiscountMode!: 'first_booking' | 'always'; + /** Up to 3 keys from the platform highlight catalog for the landing hero. */ + @Column({ name: 'hero_highlight_keys', type: 'jsonb', nullable: true }) + heroHighlightKeys!: string[] | null; + @Column({ name: 'logo_light_url', type: 'varchar', length: 500, nullable: true }) logoLightUrl!: string | null; diff --git a/src/modules/businesses/businesses.service.ts b/src/modules/businesses/businesses.service.ts index 9fd5c72..e4fe1db 100644 --- a/src/modules/businesses/businesses.service.ts +++ b/src/modules/businesses/businesses.service.ts @@ -27,6 +27,7 @@ import { AssignBusinessOwnerDto } from './dto/assign-business-owner.dto'; import { CreateBusinessDto } from './dto/create-business.dto'; import { SetAvailabilityDto } from './dto/set-availability.dto'; import { UpdateBusinessDto } from './dto/update-business.dto'; +import { sanitizeBusinessHighlightKeys } from '../../common/constants/business-highlights'; type FindAllFilters = { categoryId?: string; @@ -165,6 +166,11 @@ export class BusinessesService { Object.assign(business, payload); + if (payload.heroHighlightKeys !== undefined) { + const sanitized = sanitizeBusinessHighlightKeys(payload.heroHighlightKeys); + business.heroHighlightKeys = sanitized.length ? sanitized : null; + } + if (business.promoDiscountEnabled) { if ( !business.promoDiscountPercent || diff --git a/src/modules/businesses/dto/update-business.dto.ts b/src/modules/businesses/dto/update-business.dto.ts index 7b47a52..dda4e69 100644 --- a/src/modules/businesses/dto/update-business.dto.ts +++ b/src/modules/businesses/dto/update-business.dto.ts @@ -1,5 +1,7 @@ import { ApiPropertyOptional } from '@nestjs/swagger'; import { + IsArray, + ArrayMaxSize, IsBoolean, IsInt, IsLatitude, @@ -179,4 +181,15 @@ export class UpdateBusinessDto { @IsString() @Matches(/^(first_booking|always)$/) promoDiscountMode?: 'first_booking' | 'always'; + + @ApiPropertyOptional({ + type: [String], + example: ['customer_satisfaction', 'professional_staff', 'online_booking'], + description: 'Up to 3 highlight keys from the platform catalog for the landing hero', + }) + @IsOptional() + @IsArray() + @ArrayMaxSize(3) + @IsString({ each: true }) + heroHighlightKeys?: string[] | null; }