Add a flexible user attribute system that allows admins to define custom fields for users (text, textarea, number, email, url, date, select, multi_select types). - Add Ent schemas for UserAttributeDefinition and UserAttributeValue - Add service layer with validation logic - Add repository layer with batch operations support - Add admin API endpoints for CRUD and reorder operations - Add batch API for loading attribute values for multiple users - Add database migration (018_user_attributes.sql) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
126 lines
4.2 KiB
Go
126 lines
4.2 KiB
Go
package service
|
|
|
|
import (
|
|
"context"
|
|
"time"
|
|
|
|
infraerrors "github.com/Wei-Shaw/sub2api/internal/pkg/errors"
|
|
)
|
|
|
|
// Error definitions for user attribute operations
|
|
var (
|
|
ErrAttributeDefinitionNotFound = infraerrors.NotFound("ATTRIBUTE_DEFINITION_NOT_FOUND", "attribute definition not found")
|
|
ErrAttributeKeyExists = infraerrors.Conflict("ATTRIBUTE_KEY_EXISTS", "attribute key already exists")
|
|
ErrInvalidAttributeType = infraerrors.BadRequest("INVALID_ATTRIBUTE_TYPE", "invalid attribute type")
|
|
ErrAttributeValidationFailed = infraerrors.BadRequest("ATTRIBUTE_VALIDATION_FAILED", "attribute value validation failed")
|
|
)
|
|
|
|
// UserAttributeType represents supported attribute types
|
|
type UserAttributeType string
|
|
|
|
const (
|
|
AttributeTypeText UserAttributeType = "text"
|
|
AttributeTypeTextarea UserAttributeType = "textarea"
|
|
AttributeTypeNumber UserAttributeType = "number"
|
|
AttributeTypeEmail UserAttributeType = "email"
|
|
AttributeTypeURL UserAttributeType = "url"
|
|
AttributeTypeDate UserAttributeType = "date"
|
|
AttributeTypeSelect UserAttributeType = "select"
|
|
AttributeTypeMultiSelect UserAttributeType = "multi_select"
|
|
)
|
|
|
|
// UserAttributeOption represents a select option for select/multi_select types
|
|
type UserAttributeOption struct {
|
|
Value string `json:"value"`
|
|
Label string `json:"label"`
|
|
}
|
|
|
|
// UserAttributeValidation represents validation rules for an attribute
|
|
type UserAttributeValidation struct {
|
|
MinLength *int `json:"min_length,omitempty"`
|
|
MaxLength *int `json:"max_length,omitempty"`
|
|
Min *int `json:"min,omitempty"`
|
|
Max *int `json:"max,omitempty"`
|
|
Pattern *string `json:"pattern,omitempty"`
|
|
Message *string `json:"message,omitempty"`
|
|
}
|
|
|
|
// UserAttributeDefinition represents a custom attribute definition
|
|
type UserAttributeDefinition struct {
|
|
ID int64
|
|
Key string
|
|
Name string
|
|
Description string
|
|
Type UserAttributeType
|
|
Options []UserAttributeOption
|
|
Required bool
|
|
Validation UserAttributeValidation
|
|
Placeholder string
|
|
DisplayOrder int
|
|
Enabled bool
|
|
CreatedAt time.Time
|
|
UpdatedAt time.Time
|
|
}
|
|
|
|
// UserAttributeValue represents a user's attribute value
|
|
type UserAttributeValue struct {
|
|
ID int64
|
|
UserID int64
|
|
AttributeID int64
|
|
Value string
|
|
CreatedAt time.Time
|
|
UpdatedAt time.Time
|
|
}
|
|
|
|
// CreateAttributeDefinitionInput for creating new definition
|
|
type CreateAttributeDefinitionInput struct {
|
|
Key string
|
|
Name string
|
|
Description string
|
|
Type UserAttributeType
|
|
Options []UserAttributeOption
|
|
Required bool
|
|
Validation UserAttributeValidation
|
|
Placeholder string
|
|
Enabled bool
|
|
}
|
|
|
|
// UpdateAttributeDefinitionInput for updating definition
|
|
type UpdateAttributeDefinitionInput struct {
|
|
Name *string
|
|
Description *string
|
|
Type *UserAttributeType
|
|
Options *[]UserAttributeOption
|
|
Required *bool
|
|
Validation *UserAttributeValidation
|
|
Placeholder *string
|
|
Enabled *bool
|
|
}
|
|
|
|
// UpdateUserAttributeInput for updating a single attribute value
|
|
type UpdateUserAttributeInput struct {
|
|
AttributeID int64
|
|
Value string
|
|
}
|
|
|
|
// UserAttributeDefinitionRepository interface for attribute definition persistence
|
|
type UserAttributeDefinitionRepository interface {
|
|
Create(ctx context.Context, def *UserAttributeDefinition) error
|
|
GetByID(ctx context.Context, id int64) (*UserAttributeDefinition, error)
|
|
GetByKey(ctx context.Context, key string) (*UserAttributeDefinition, error)
|
|
Update(ctx context.Context, def *UserAttributeDefinition) error
|
|
Delete(ctx context.Context, id int64) error
|
|
List(ctx context.Context, enabledOnly bool) ([]UserAttributeDefinition, error)
|
|
UpdateDisplayOrders(ctx context.Context, orders map[int64]int) error
|
|
ExistsByKey(ctx context.Context, key string) (bool, error)
|
|
}
|
|
|
|
// UserAttributeValueRepository interface for user attribute value persistence
|
|
type UserAttributeValueRepository interface {
|
|
GetByUserID(ctx context.Context, userID int64) ([]UserAttributeValue, error)
|
|
GetByUserIDs(ctx context.Context, userIDs []int64) ([]UserAttributeValue, error)
|
|
UpsertBatch(ctx context.Context, userID int64, values []UpdateUserAttributeInput) error
|
|
DeleteByAttributeID(ctx context.Context, attributeID int64) error
|
|
DeleteByUserID(ctx context.Context, userID int64) error
|
|
}
|