aurganize-backend/backend/internal/models/users.go

364 lines
13 KiB
Go

package models
import (
"time"
"github.com/google/uuid"
)
// User represents a user account in the system.
// This is the core user entity containing authentication and profile information.
//
// What is a user?
// - Account with unique email
// - Associated with a tenant (multi-tenancy)
// - Has roles for authorization
// - Contains profile information
// - Tracks authentication state
//
// User lifecycle:
// 1. Created: During registration
// 2. Pending: Email verification required (optional)
// 3. Active: Can log in and use system
// 4. Suspended: Temporarily disabled
// 5. Deleted: Soft deleted (deleted_at set)
//
// Multi-tenancy:
// - Each user belongs to one tenant (organization)
// - Tenant ID used for data isolation
// - Cross-tenant access not allowed
// - Important for SaaS applications
//
// Security considerations:
// - Password never exposed (json:"-" tag)
// - Soft delete preserves data integrity
// - Email is unique identifier
// - Role-based access control
type User struct {
// ID is the unique identifier for this user
// Type: UUID v4 (128-bit random identifier)
// Generated: Automatically by database on insert
// Primary key: Used for all lookups and relationships
ID uuid.UUID `json:"id" bson:"id"`
// TenantID identifies which organization this user belongs to
// Type: UUID v4 (foreign key to tenants table)
// Multi-tenancy: Isolates data between organizations
// Required: Every user must belong to a tenant
// Used for: Filtering queries, enforcing data isolation
TenantID uuid.UUID `json:"tenant_id" db:"tenant_id"`
// Email is the user's email address (unique identifier for login)
// Type: String (validated format, max 254 chars)
// Unique: Within system (can't have duplicate accounts)
// Normalized: Stored as lowercase for consistent matching
// Used for: Login, communication, uniqueness
// Privacy: PII, must be protected
Email string `json:"email" db:"email"`
// PasswordHash is the bcrypt hash of the user's password
// Type: Optional string (can be null for social login)
// Format: "$2a$10$..." (bcrypt hash with salt)
// Security: NEVER expose in API responses (json:"-" means omit)
// Hashing: Bcrypt with cost factor 10
// Why optional: Users with social login may not have password
PasswordHash *string `json:"_" db:"password_hash"`
// FirstName is the user's first name
// Type: Optional string (can be null)
// Used for: Personalization, display
// Privacy: PII, must be protected
FirstName *string `json:"first_name" db:"first_name"`
// LastName is the user's last name
// Type: Optional string (can be null)
// Used for: Personalization, display
// Privacy: PII, must be protected
LastName *string `json:"last_name" db:"last_name"`
// FullName is the computed full name (first + last)
// Type: String (computed by database trigger or application)
// Generated: From first_name and last_name
// Used for: Display purposes, searching
// Database: Might be a generated column or manually updated
FullName string `json:"full_name" db:"full_name"`
// AvatarURL is the URL to the user's profile picture
// Type: Optional string (can be null)
// Storage: URL points to file in object storage (MinIO/S3)
// Default: System can provide default avatar if null
// Privacy: Publicly accessible or requires auth
AvatarURL *string `json:"avatar_url" db:"avatar_url"`
// Phone is the user's phone number
// Type: Optional string (can be null)
// Format: Should be E.164 format (+1234567890)
// Used for: 2FA, notifications, contact
// Privacy: PII, must be protected
// Verification: Should require phone verification
Phone *string `json:"phone" db:"phone"`
// Role defines the user's permissions level
// Type: String (enum-like values)
// Common values: "admin", "user", "manager", "viewer"
// Used for: Authorization checks, feature access
// Default: Usually "user" for new accounts
// Important: Always check role before allowing operations
Role string `json:"role" db:"role"`
// Status indicates the current state of the account
// Type: String (enum-like values)
// Possible values: "active", "pending", "suspended", "deleted"
// Active: Can log in normally
// Pending: Awaiting email verification
// Suspended: Temporarily disabled (can't log in)
// Deleted: Soft deleted (should have deleted_at set)
Status string `json:"status" db:"status"`
// EmailVerified indicates if email has been verified
// Type: Boolean (default false)
// Purpose: Confirm user owns the email address
// Workflow: User clicks link in verification email
// Requirement: Some systems require verification before full access
EmailVerified bool `json:"email_verified" db:"email_verified"`
// EmailVerifiedAt is when the email was verified
// Type: Optional timestamp (null if not verified)
// Set when: User clicks verification link
// Used for: Audit trail, resend logic
EmailVerifiedAt *time.Time `json:"email_verified_at" db:"email_verified_at"`
// IsOnboarded indicates if user completed onboarding
// Type: Boolean (default false)
// Purpose: Track if user saw welcome/tutorial
// Used for: Showing onboarding flow
// Set to true: After user completes onboarding steps
IsOnboarded bool `json:"is_onboarded" db:"is_onboarded"`
// LastLoginAt is when the user last logged in
// Type: Optional timestamp (null if never logged in)
// Updated: After successful authentication
// Used for: Security monitoring, activity tracking
// Display: "Last login: 2 hours ago"
LastLoginAt *time.Time `json:"last_login_at" db:"last_login_at"`
// LastLoginIP is the IP address of last login
// Type: Optional string (null if never logged in)
// Format: IPv4 or IPv6
// Used for: Security monitoring, anomaly detection
// Privacy: PII under GDPR, may need anonymization
LastLoginIP *string `json:"last_login_ip" db:"last_login_ip"`
// CreatedAt is when the user account was created
// Type: Timestamp (automatically set by database)
// Used for: Account age, analytics, sorting
// Database: Set by DEFAULT NOW() in schema
CreatedAt time.Time `json:"created_at" db:"created_at"`
// UpdatedAt is when the user record was last modified
// Type: Timestamp (automatically updated)
// Updated: Any time user record changes
// Used for: Audit trail, change tracking
// Database: Updated by trigger or application
UpdatedAt time.Time `json:"updated_at" db:"updated_at"`
// DeletedAt is when the user was soft deleted
// Type: Optional timestamp (null if not deleted)
// Soft delete: Record preserved but marked as deleted
// Used for: Data integrity, audit trail
// Omitted from JSON if null (json:"deleted_at,omitempty")
// Queries: Filter WHERE deleted_at IS NULL
DeletedAt *time.Time `json:"deleted_at,omitempty" db:"deleted_at"`
}
// CreateUserInput contains the data needed to create a new user account.
// This is a DTO (Data Transfer Object) for the registration/user creation flow.
//
// Why separate input struct?
// - Separates client input from database-generated fields
// - Clear interface for what's required vs what's generated
// - Type safety (can't set ID, timestamps, etc.)
// - Validation happens on this struct
//
// When used:
// - User registration
// - Admin creating user
// - Invitation flow
type CreateUserInput struct {
// TenantID is which organization the user belongs to
// Required: Every user must belong to a tenant
// Set by: System (based on signup domain, invitation, etc.)
TenantID uuid.UUID
// Email is the user's email address
// Required: Used for login and communication
// Validation: Must be valid email format, must be unique
// Normalized: Will be lowercased before storage
Email string
// Password is the plaintext password
// Required: Must meet strength requirements
// Security: Will be hashed with bcrypt before storage
// Validation: Checked for length, complexity, common patterns
// NEVER logged or stored plaintext
Password string
// FirstName is the user's first name
// Optional: Can be null (but recommended)
// Used for: Personalization, display
FirstName *string
// LastName is the user's last name
// Optional: Can be null (but recommended)
// Used for: Personalization, display
LastName *string
// Role is the user's permission level
// Required: Must be set (often defaults to "user")
// Values: "admin", "user", "manager", etc.
// Set by: System (based on registration type) or admin
Role string
// Status is the initial account status
// Required: Usually "pending" or "active"
// "pending": Requires email verification
// "active": Can log in immediately
// Set by: System based on email verification policy
Status string
}
// UserResponse is a sanitized user object for API responses.
// This DTO removes sensitive fields before sending to client.
//
// Why separate response struct?
// - Security: Never expose password_hash or internal IDs
// - API contract: Clear definition of what clients receive
// - Flexibility: Can add computed fields without changing User model
// - Versioning: Can have different responses for API versions
//
// What's excluded from User:
// - PasswordHash: NEVER expose password hashes
// - DeletedAt: Internal field, not relevant to client
// - Internal IDs: Some may be excluded depending on use case
//
// When used:
// - Login response
// - Profile endpoint
// - User list endpoints
// - Any API response containing user data
type UserResponse struct {
// ID is the user's unique identifier
ID uuid.UUID `json:"id"`
// TenantID for multi-tenancy
TenantID uuid.UUID `json:"tenant_id"`
// Email for display and communication
Email string `json:"email"`
// FirstName for personalization
// Note: JSON tag shows "name" but field is FirstName (might be typo)
FirstName *string `json:"name"`
// LastName for full name display
LastName *string `json:"last_name"`
// FullName computed from first + last
FullName string `json:"full_name"`
// AvatarURL for profile picture
AvatarURL *string `json:"avatar_url"`
// Phone for contact
Phone *string `json:"phone"`
// Role for client-side permission checks
Role string `json:"role"`
// Status to show account state
Status string `json:"status"`
// EmailVerified to prompt verification if needed
EmailVerified bool `json:"email_verified"`
// IsOnboarded to show onboarding if needed
IsOnboarded bool `json:"is_onboarded"`
// LastLoginAt for security awareness
LastLoginAt *time.Time `json:"last_login_at"`
// CreatedAt for account age
CreatedAt time.Time `json:"created_at"`
// Note: Excludes PasswordHash, DeletedAt, UpdatedAt, LastLoginIP
}
// ToResponse converts a User model to a UserResponse DTO.
// This method sanitizes the user object before sending to client.
//
// Why this method?
// - Encapsulation: Conversion logic lives with the model
// - Reusability: Can be called anywhere needed
// - Type safety: Returns correct response type
// - Maintainability: One place to update response structure
//
// Usage:
//
// user := getUserFromDB()
// response := user.ToResponse()
// return c.JSON(http.StatusOK, response)
//
// What it does:
// - Copies public fields from User to UserResponse
// - Excludes sensitive fields (password_hash)
// - Excludes internal fields (deleted_at, updated_at, last_login_ip)
//
// Returns:
// - UserResponse with safe fields populated
func (u *User) ToResponse() *UserResponse {
return &UserResponse{
ID: u.ID,
TenantID: u.TenantID,
Email: u.Email,
FirstName: u.FirstName,
LastName: u.LastName,
FullName: u.FullName,
AvatarURL: u.AvatarURL,
Phone: u.Phone,
Role: u.Role,
Status: u.Status,
EmailVerified: u.EmailVerified,
IsOnboarded: u.IsOnboarded,
LastLoginAt: u.LastLoginAt,
CreatedAt: u.CreatedAt,
// Intentionally excluded:
// - PasswordHash (security)
// - LastLoginIP (privacy)
// - DeletedAt (internal)
// - UpdatedAt (internal)
// - EmailVerifiedAt (redundant with EmailVerified boolean)
}
}
// Note about database migration:
// The comment at the end of the original file mentions:
// "Current DB structure need to updated the migration script to reflect the current UserEntity"
//
// This suggests the database schema may be out of sync with this model.
// The old structure mentioned was:
// id | tenant_id | email | password_hash | name | avatar_url | role | is_active |
// email_verified_at | last_login_at | created_at | updated_at | deleted_at
//
// Differences from current model:
// 1. "name" field vs "first_name" + "last_name" + "full_name"
// 2. "is_active" field vs "status" field (enum)
// 3. Missing "phone" field
// 4. Missing "email_verified" boolean
// 5. Missing "is_onboarded" boolean
// 6. Missing "last_login_ip" field
//
// Action required:
// - Create database migration to update schema
// - Or update model to match current database (then migrate later)
// - Ensure model and database stay in sync