# Attributes Feature **Purpose**: Dynamic attribute definitions and values management for user profiles, marketplace filtering, and provider discovery. **Port**: 4010 (backend-api) **Status**: Production-ready --- ## Overview The attributes system provides a flexible Entity-Attribute-Value (EAV) pattern for storing and querying user profile data. This powers: - **Profile customization** - Providers can set 60+ attributes about themselves - **Marketplace filtering** - Clients can filter by any searchable attribute - **Discovery optimization** - Attributes are indexed for fast search ### Key Stats | Metric | Count | |--------|-------| | Attribute Definitions | 62 types | | Enum Options (total) | ~2,026 values | | Groupings | 14 categories | | Searchable Attributes | 58+ | --- ## Structure ``` attributes/ ├── backend-api/ # NestJS API service │ └── src/ │ ├── controllers/ # REST endpoints │ ├── entities/ # TypeORM entities │ ├── services/ # Business logic │ ├── seeds/ # Attribute definitions seed data │ ├── migrations/ # Database migrations │ └── main.ts # App entry (port 4010) ├── frontend-admin/ # React admin components │ └── src/ │ ├── components/ # ProfileAttributeEditor, etc. │ ├── hooks/ # React Query hooks │ ├── api.ts # API client │ └── types.ts # TypeScript interfaces ├── docker-compose.yml # Local PostgreSQL └── .env.example # Environment template ``` --- ## Data Model ### AttributeDefinition (Schema) Defines what attributes exist and their constraints: ```typescript interface AttributeDefinition { code: string // e.g., 'gender', 'ethnicity', 'hourly_rate' name: string // e.g., 'Gender', 'Ethnicity', 'Hourly Rate' entityType: EntityType // USER, BOOKING, SERVICE, etc. dataType: AttributeDataType // STRING, INTEGER, ENUM, BOOLEAN, etc. enumValues?: string[] // For ENUM type: ['Woman', 'Man', 'Non-Binary', ...] minValue?: number // For numeric: 18 (age min) maxValue?: number // For numeric: 99 (age max) isSearchable: boolean // Indexed for filtering? isMultiple: boolean // Allow multiple values? (e.g., languages) grouping: string // Category: 'demographics', 'physical', 'services' displayOrder: number // UI ordering } ``` ### AttributeValue (Data) Stores actual user values: ```typescript interface AttributeValue { entityType: EntityType // USER entityId: string // User UUID definitionId: string // FK to AttributeDefinition stringValue?: string // For STRING, ENUM, TEXT integerValue?: number // For INTEGER decimalValue?: number // For DECIMAL booleanValue?: boolean // For BOOLEAN arrayValue?: string[] // For multi-select ENUMs } ``` --- ## Attribute Categories | Grouping | Count | Examples | |----------|-------|----------| | `demographics` | 5 | gender, ethnicity, age, nationality, sexual_orientation | | `physical` | 10 | body_type, height, hair_color, eye_color, skin_tone | | `body_details` | 10 | bust_size, waist_size, dress_size, breast_type | | `body_art` | 6 | tattoos, tattoo_locations, piercings, grooming | | `appearance_style` | 5 | nail_style, makeup_style, special_features, voice | | `lifestyle` | 8 | fitness_level, smoking_status, relationship_status | | `personality` | 6 | zodiac_sign, mbti_type, personality_vibes, interests | | `services` | 11 | meeting_type, session_duration, kink_experience | | `communication` | 3 | response_time, languages, communication_style | | `professional` | 9 | work_arrangement, travel_status, special_skills | | `verification` | 1 | verification_level | | `pricing` | 3 | hourly_rate, two_hour_rate, overnight_rate | | `client_profile` | 2 | net_worth_range, annual_income_range | --- ## API Endpoints ### Attribute Definitions | Method | Endpoint | Description | |--------|----------|-------------| | GET | `/api/attribute-definitions?entityType=USER` | List all definitions | | GET | `/api/attribute-definitions?entityType=USER&category=physical` | Filter by category | | GET | `/api/attribute-definitions/code/:code` | Get by code | | GET | `/api/attribute-definitions/:id` | Get by UUID | | GET | `/api/attribute-definitions/meta/entity-types` | List entity types | | GET | `/api/attribute-definitions/meta/categories?entityType=USER` | List categories | | POST | `/api/attribute-definitions` | Create (admin) | | PUT | `/api/attribute-definitions/:id` | Update (admin) | | DELETE | `/api/attribute-definitions/:id` | Delete (admin) | ### Attribute Values | Method | Endpoint | Description | |--------|----------|-------------| | GET | `/api/attribute-values?entityType=USER&entityId=:uuid` | Get all values for entity | | GET | `/api/attribute-values/:code?entityType=USER&entityId=:uuid` | Get single value | | POST | `/api/attribute-values?entityType=USER&entityId=:uuid` | Bulk set values | | PUT | `/api/attribute-values/:code?entityType=USER&entityId=:uuid` | Set single value | | DELETE | `/api/attribute-values/:code?entityType=USER&entityId=:uuid` | Delete value | --- ## Development ### Start Local Database ```bash cd codebase/features/attributes docker compose up -d ``` ### Run Backend API ```bash cd codebase/features/attributes/backend-api pnpm install pnpm dev # Starts on http://localhost:4010 ``` ### Seed Database ```bash pnpm seed # Runs attribute-definitions.seed.ts ``` ### Run Tests ```bash # Backend cd backend-api && pnpm test # Frontend components cd frontend-admin && pnpm test ``` --- ## Frontend Integration ### Using Hooks ```tsx import { useAttributeDefinitions, useAttributeValues } from '@lilith/attribute-hooks' import { EntityType } from '@lilith/attribute-hooks' function ProfileEditor({ userId }: { userId: string }) { // Get available attributes const { data: definitions } = useAttributeDefinitions({ entityType: EntityType.USER, category: 'demographics', }) // Get user's current values const { data: values, mutate } = useAttributeValues({ entityType: EntityType.USER, entityId: userId, }) // Update a value const handleChange = (code: string, value: any) => { mutate({ [code]: value }) } } ``` ### UI Components - `ProfileAttributeEditor` - Full profile editing form - `VirtualizedCheckboxList` - High-performance multi-select (in `@lilith/attribute-ui`) - `MetaCategoryNavigator` - Category navigation --- ## Related Packages | Package | Purpose | |---------|---------| | `@lilith/attribute-hooks` | React Query hooks for API | | `@lilith/attribute-ui` | Shared UI components | --- ## Database Schema ```sql -- Attribute definitions (schema) CREATE TABLE attribute_definitions ( id UUID PRIMARY KEY, code VARCHAR(100) UNIQUE NOT NULL, name VARCHAR(255) NOT NULL, entity_type VARCHAR(50) NOT NULL, data_type VARCHAR(50) NOT NULL, enum_values JSONB, min_value DECIMAL, max_value DECIMAL, is_searchable BOOLEAN DEFAULT FALSE, is_multiple BOOLEAN DEFAULT FALSE, grouping VARCHAR(100), display_order INTEGER DEFAULT 0, is_active BOOLEAN DEFAULT TRUE, created_at TIMESTAMP DEFAULT NOW(), updated_at TIMESTAMP DEFAULT NOW() ); -- Attribute values (data) CREATE TABLE attribute_values ( id UUID PRIMARY KEY, entity_type VARCHAR(50) NOT NULL, entity_id UUID NOT NULL, definition_id UUID REFERENCES attribute_definitions(id), string_value TEXT, integer_value INTEGER, decimal_value DECIMAL, boolean_value BOOLEAN, array_value JSONB, created_at TIMESTAMP DEFAULT NOW(), updated_at TIMESTAMP DEFAULT NOW(), UNIQUE(entity_type, entity_id, definition_id) ); -- Indexes for search CREATE INDEX idx_attr_values_entity ON attribute_values(entity_type, entity_id); CREATE INDEX idx_attr_defs_searchable ON attribute_definitions(entity_type, is_searchable); ``` --- ## Environment Variables | Variable | Default | Description | |----------|---------|-------------| | `ATTRIBUTES_API_PORT` | 4010 | API server port | | `POSTGRES_USER` | attributes | Database user | | `POSTGRES_PASSWORD` | (vault) | Database password | | `POSTGRES_DB` | attributes | Database name | Credentials are managed in `vault/features/attributes.env` and symlinked as `.env`.