platform-codebase/features/attributes/README.md

275 lines
8.3 KiB
Markdown
Executable file

# 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`.