275 lines
8.3 KiB
Markdown
Executable file
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`.
|