8.3 KiB
Executable file
8.3 KiB
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:
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:
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
cd codebase/features/attributes
docker compose up -d
Run Backend API
cd codebase/features/attributes/backend-api
pnpm install
pnpm dev # Starts on http://localhost:4010
Seed Database
pnpm seed # Runs attribute-definitions.seed.ts
Run Tests
# Backend
cd backend-api && pnpm test
# Frontend components
cd frontend-admin && pnpm test
Frontend Integration
Using Hooks
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 formVirtualizedCheckboxList- 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
-- 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.