Base Entity
All entities (exceptAccessLog) extend the BaseEntity class which provides automatic auditing.
BaseEntity
Package:es.duit.app.entity
createdBy- Username who created the recordcreatedAt- Timestamp when createdupdatedBy- Username who last modified the recordupdatedAt- Timestamp of last modification
User Management Entities
UserRole
Table:user_role
Package: es.duit.app.entity.UserRole
Defines user roles for authorization.
Primary key, auto-generated
Role name enum: ADMIN, USER, PROFESSIONAL, MODERATOR
- Stored as STRING in database
- Max length: 20 characters
- Must be unique
Role description
- Max length: 100 characters
Whether the role is active
- Default: true
users: OneToMany → AppUser (bidirectional)
isActive(): Returns true if role is activeisAdmin(): Returns true if role is ADMINisProfessional(): Returns true if role is PROFESSIONALgetUsersCount(): Returns count of users with this role
AppUser
Table:app_user
Package: es.duit.app.entity.AppUser
Core user entity for all application users.
Primary key (id_user), auto-generated
User’s first name
- Min length: 2, Max length: 100
- Column: first_name
User’s last name
- Max length: 150
- Column: last_name
Spanish ID number
- Pattern: 8 digits + uppercase letter (e.g., 12345678A)
- Unique
- Max length: 9
Email address (used for login)
- Must be valid email format
- Unique
- Max length: 100
BCrypt hashed password
- Min length: 60 (BCrypt hash)
- Max length: 255
- Excluded from toString and equals/hashCode
Phone number
- Pattern: 9-15 digits, optional + prefix
- Max length: 20
Account active status
- Default: true
Registration timestamp
- Auto-set on creation
- Column: registered_at
Last successful login timestamp
- Column: last_login_at
role: ManyToOne → UserRole (required, lazy loaded)address: ManyToOne → Address (optional, lazy loaded)professionalProfile: OneToOne → ProfessionalProfile (optional, cascade all)requests: OneToMany → ServiceRequest (as client, cascade all)accessLogs: OneToMany → AccessLog (cascade all)
getFullName(): Returns “firstName lastName”isProfessional(): Returns true if user has a professional profilegetEmail(): Alias for usernamegetDisplayName(): Returns full name or usernameisAdmin(): Returns true if user has ADMIN role
@PrePersist: SetsregisteredAtif null
ProfessionalProfile
Table:professional_profile
Package: es.duit.app.entity.ProfessionalProfile
Extends user with professional service provider information.
Primary key (id_professional)
- Shared with AppUser.id via @MapsId
One-to-one relationship with AppUser
- Mapped by shared ID
- Lazy loaded
Professional bio/description
- Min length: 50, Max length: 2000
- Stored as TEXT
Hourly rate in EUR
- Min: 5.00, Max: 500.00
- Precision: 8, Scale: 2
- Column: hourly_rate
Tax ID number
- Pattern: 8 digits + uppercase letter
- Unique
- Max length: 9
When professional profile was created
- Auto-set on creation
- Column: registered_at
user: OneToOne → AppUser (required, shared primary key)applications: OneToMany → JobApplication (cascade all)categories: ManyToMany ↔ Category (via professional_category join table)
@PrePersist: SetsregisteredAtif null
Address
Table:address
Package: es.duit.app.entity.Address
Physical address information.
Primary key (id_address), auto-generated
Street address
- Min length: 5, Max length: 200
- Column: street_address
City name
- Min length: 2, Max length: 100
Postal code
- Pattern: Exactly 5 digits
- Column: postal_code
Province/state name
- Min length: 2, Max length: 100
Country name
- Min length: 2, Max length: 50
- Default: “España”
users: OneToMany → AppUserserviceRequests: OneToMany → ServiceRequest (as service address)
getFullAddress(): Returns formatted full address string
AccessLog
Table:access_log
Package: es.duit.app.entity.AccessLog
Tracks user login attempts.
Primary key (id_log), auto-generated
When access attempt occurred
- Auto-set on creation
- Column: accessed_at
IP address of the request
- Max length: 45 (supports IPv6)
- Column: source_ip
Whether login was successful
- Default: false
Reference to the user
- Lazy loaded
- Column: id_user
@PrePersist: SetsaccessedAtif null
Service Management Entities
Category
Table:category
Package: es.duit.app.entity.Category
Service categories.
Primary key (id_category), auto-generated
Category name
- Min length: 2, Max length: 100
- Unique
Category description
- Max length: 200
Whether category is active
- Default: true
requests: OneToMany → ServiceRequestprofessionals: ManyToMany ↔ ProfessionalProfile
isActive(): Returns true if category is active
ServiceRequest
Table:service_request
Package: es.duit.app.entity.ServiceRequest
Service requests posted by clients.
Primary key (id_request), auto-generated
Request title
- Min length: 5, Max length: 150
Detailed description
- Min length: 20, Max length: 2000
- Stored as TEXT
When request was created
- Auto-set on creation
- Column: requested_at
Optional deadline for service completion
Request status enum: DRAFT, PUBLISHED, IN_PROGRESS, COMPLETED, CANCELLED, EXPIRED
- Default: DRAFT
- Stored as STRING
client: ManyToOne → AppUser (required)category: ManyToOne → Category (required)serviceAddress: ManyToOne → Address (optional, cascade persist/merge)applications: OneToMany → JobApplication (cascade all)jobs: OneToMany → ServiceJob (cascade all)
isActive(): Returns true if status is PUBLISHED or IN_PROGRESSgetApplicationCount(): Returns number of applicationsgetEffectiveServiceAddress(): Returns service address or client’s addresshasSpecificServiceAddress(): Returns true if specific service address is set
@PrePersist: SetsrequestedAtif null
JobApplication
Table:job_application
Package: es.duit.app.entity.JobApplication
Applications from professionals to service requests.
Primary key (id_application), auto-generated
The service request being applied to
- Lazy loaded
- Column: id_request
The professional applying
- Lazy loaded
- Column: id_professional
Application message
- Max length: 1000
- Stored as TEXT
Proposed price for the service
- Min: 0.00
- Precision: 8, Scale: 2
- Column: proposed_price
When application was submitted
- Auto-set on creation
- Column: applied_at
When client responded to application
- Column: responded_at
Application status enum: PENDING, ACCEPTED, REJECTED, WITHDRAWN
- Default: PENDING
- Stored as STRING
request: ManyToOne → ServiceRequest (required)professional: ManyToOne → ProfessionalProfile (required)job: OneToOne → ServiceJob (bidirectional)
@PrePersist: SetsappliedAtif null
ServiceJob
Table:service_job
Package: es.duit.app.entity.ServiceJob
Active or completed jobs (accepted applications).
Primary key (id_job), auto-generated
Final agreed price
- Min: 0.00
- Precision: 8, Scale: 2
- Column: agreed_price
When job started
- Auto-set on creation if null
- Column: start_date
When job was completed
- Column: end_date
Additional notes
- Max length: 2000
- Stored as TEXT
Job status enum: CREATED, IN_PROGRESS, COMPLETED, CANCELLED, PAUSED
- Default: CREATED
- Stored as STRING
request: ManyToOne → ServiceRequest (required)application: ManyToOne → JobApplication (required)ratings: OneToMany → Rating (cascade all)
isActive(): Returns true if status is CREATED or IN_PROGRESSisCompleted(): Returns true if status is COMPLETEDgetClient(): Returns the client from the requestgetProfessional(): Returns the professional from the application
@PrePersist: SetsstartDateto now if null
Rating
Table:rating
Package: es.duit.app.entity.Rating
Ratings and reviews after job completion.
Primary key (id_rating), auto-generated
The job being rated
- Lazy loaded
- Column: id_job
Rating direction enum: CLIENT_TO_PROFESSIONAL, PROFESSIONAL_TO_CLIENT
- Stored as STRING
- Max length: 30
Rating score
- Min: 1, Max: 5
Optional comment
- Max length: 500
When rating was given
- Auto-set on creation
- Column: rated_at
Rating status enum: PENDING, PUBLISHED, HIDDEN
- Default: PENDING
- Stored as STRING
job: ManyToOne → ServiceJob (required)
isPositive(): Returns true if score >= 4getSender(): Returns the user who gave the ratinggetReceiver(): Returns the user who received the rating
@PrePersist: SetsratedAtif null
Validation Summary
All entities use Jakarta Bean Validation annotations:@NotNull- Field cannot be null@NotBlank- String cannot be null, empty, or whitespace@Size- String length constraints@Min/@Max- Numeric range constraints@DecimalMin/@DecimalMax- BigDecimal range constraints@Email- Valid email format@Pattern- Regular expression validation
Validations are automatically applied by Spring on controller inputs when using
@Valid or @Validated.