Skip to main content

Schema Overview

Duit uses PostgreSQL as its relational database. The schema consists of 9 main tables with well-defined relationships and indexes for optimal query performance.

Entity-Relationship Diagram

┌──────────────┐
│  UserRole    │
└──────┬───────┘
       │ 1

       │ N
┌──────▼──────────────┐       ┌────────────────┐
│     AppUser         │───────│  AccessLog     │
└──────┬──────────────┘  1:N  └────────────────┘
       │ 1:1


┌──────────────────────┐      ┌─────────────────┐
│ ProfessionalProfile  │◄─────│   Category      │
└──────┬───────────────┘ M:N  └─────────┬───────┘
       │ 1                              │ 1
       │                                │
       │ N                              │ N
┌──────▼───────────────┐      ┌─────────▼────────┐
│  JobApplication      │──────│ ServiceRequest   │
└──────┬───────────────┘ N:1  └─────────┬────────┘
       │ 1:1                            │
       │                                │
       ▼                                ▼
┌──────────────────────┐                │
│    ServiceJob        │◄───────────────┘
└──────┬───────────────┘      1:N
       │ 1

       │ N
┌──────▼───────────────┐
│      Rating          │
└──────────────────────┘

┌──────────────────────┐
│     Address          │ (Referenced by AppUser & ServiceRequest)
└──────────────────────┘

Core Tables

user_role

Stores user roles for authorization.
ColumnTypeConstraintsDescription
id_roleBIGINTPRIMARY KEY, AUTO_INCREMENTUnique role identifier
nameVARCHAR(20)NOT NULL, UNIQUERole name (ADMIN, USER, PROFESSIONAL, MODERATOR)
descriptionVARCHAR(100)Role description
activeBOOLEANNOT NULL, DEFAULT TRUEWhether the role is active
created_byVARCHAR(100)Audit field
created_atTIMESTAMPAudit field
updated_byVARCHAR(100)Audit field
updated_atTIMESTAMPAudit field
Indexes:
  • idx_role_name on name
  • idx_role_active on active

app_user

Core user table for all application users.
ColumnTypeConstraintsDescription
id_userBIGINTPRIMARY KEY, AUTO_INCREMENTUnique user identifier
first_nameVARCHAR(100)NOT NULLUser’s first name
last_nameVARCHAR(150)User’s last name
dniVARCHAR(9)UNIQUESpanish ID (8 digits + letter)
usernameVARCHAR(100)NOT NULL, UNIQUEEmail address (used for login)
passwordVARCHAR(255)NOT NULLBCrypt hashed password
phoneVARCHAR(20)Phone number
activeBOOLEANNOT NULL, DEFAULT TRUEAccount active status
registered_atTIMESTAMPRegistration timestamp
last_login_atTIMESTAMPLast successful login
id_roleBIGINTFOREIGN KEY, NOT NULLReference to user_role
id_addressBIGINTFOREIGN KEYReference to address
created_byVARCHAR(100)Audit field
created_atTIMESTAMPAudit field
updated_byVARCHAR(100)Audit field
updated_atTIMESTAMPAudit field
Indexes:
  • idx_user_username on username
  • idx_user_dni on dni
  • idx_user_active on active
  • idx_user_role on id_role
Relationships:
  • ManyToOneuser_role
  • ManyToOneaddress
  • OneToOneprofessional_profile
  • OneToManyservice_request (as client)
  • OneToManyaccess_log

professional_profile

Extends user information for professionals offering services.
ColumnTypeConstraintsDescription
id_professionalBIGINTPRIMARY KEY, FOREIGN KEYReferences app_user.id_user
descriptionTEXTNOT NULLProfessional bio (50-2000 chars)
hourly_rateDECIMAL(8,2)NOT NULLHourly rate (5.00-500.00 EUR)
nifVARCHAR(9)NOT NULL, UNIQUETax ID (8 digits + letter)
registered_atTIMESTAMPWhen professional profile was created
created_byVARCHAR(100)Audit field
created_atTIMESTAMPAudit field
updated_byVARCHAR(100)Audit field
updated_atTIMESTAMPAudit field
Indexes:
  • idx_professional_nif on nif
  • idx_professional_hourly_rate on hourly_rate
Relationships:
  • OneToOneapp_user (shared primary key)
  • ManyToManycategory (via professional_category join table)
  • OneToManyjob_application

category

Service categories that professionals can specialize in.
ColumnTypeConstraintsDescription
id_categoryBIGINTPRIMARY KEY, AUTO_INCREMENTUnique category identifier
nameVARCHAR(100)NOT NULL, UNIQUECategory name
descriptionVARCHAR(200)Category description
activeBOOLEANNOT NULL, DEFAULT TRUEWhether category is active
created_byVARCHAR(100)Audit field
created_atTIMESTAMPAudit field
updated_byVARCHAR(100)Audit field
updated_atTIMESTAMPAudit field
Indexes:
  • idx_category_name on name
  • idx_category_active on active
Relationships:
  • ManyToManyprofessional_profile
  • OneToManyservice_request

address

Physical addresses for users and service locations.
ColumnTypeConstraintsDescription
id_addressBIGINTPRIMARY KEY, AUTO_INCREMENTUnique address identifier
street_addressVARCHAR(200)NOT NULLStreet address
cityVARCHAR(100)NOT NULLCity name
postal_codeVARCHAR(5)5-digit postal code
provinceVARCHAR(100)NOT NULLProvince/state
countryVARCHAR(50)NOT NULL, DEFAULT ‘España’Country name
created_byVARCHAR(100)Audit field
created_atTIMESTAMPAudit field
updated_byVARCHAR(100)Audit field
updated_atTIMESTAMPAudit field
Indexes:
  • idx_address_city on city
  • idx_address_postal on postal_code
  • idx_address_province on province
  • idx_address_location on city, province
  • idx_address_full_location on city, postal_code, province
Relationships:
  • OneToManyapp_user
  • OneToManyservice_request (as service address)

Service Management Tables

service_request

Service requests posted by clients.
ColumnTypeConstraintsDescription
id_requestBIGINTPRIMARY KEY, AUTO_INCREMENTUnique request identifier
titleVARCHAR(150)NOT NULLRequest title (5-150 chars)
descriptionTEXTNOT NULLDetailed description (20-2000 chars)
requested_atTIMESTAMPWhen request was created
deadlineTIMESTAMPOptional deadline
statusVARCHAR(20)NOT NULLDRAFT, PUBLISHED, IN_PROGRESS, COMPLETED, CANCELLED, EXPIRED
id_clientBIGINTFOREIGN KEY, NOT NULLReference to app_user
id_categoryBIGINTFOREIGN KEY, NOT NULLReference to category
id_service_addressBIGINTFOREIGN KEYOptional specific service address
created_byVARCHAR(100)Audit field
created_atTIMESTAMPAudit field
updated_byVARCHAR(100)Audit field
updated_atTIMESTAMPAudit field
Indexes:
  • idx_request_category on id_category
  • idx_request_client on id_client
  • idx_request_status on status
  • idx_request_service_address on id_service_address
Relationships:
  • ManyToOneapp_user (client)
  • ManyToOnecategory
  • ManyToOneaddress (service address)
  • OneToManyjob_application
  • OneToManyservice_job

job_application

Applications from professionals to service requests.
ColumnTypeConstraintsDescription
id_applicationBIGINTPRIMARY KEY, AUTO_INCREMENTUnique application identifier
id_requestBIGINTFOREIGN KEY, NOT NULLReference to service_request
id_professionalBIGINTFOREIGN KEY, NOT NULLReference to professional_profile
messageTEXTApplication message (max 1000 chars)
proposed_priceDECIMAL(8,2)Proposed price for the service
applied_atTIMESTAMPWhen application was submitted
responded_atTIMESTAMPWhen client responded
statusVARCHAR(20)NOT NULLPENDING, ACCEPTED, REJECTED, WITHDRAWN
created_byVARCHAR(100)Audit field
created_atTIMESTAMPAudit field
updated_byVARCHAR(100)Audit field
updated_atTIMESTAMPAudit field
Indexes:
  • idx_application_request on id_request
  • idx_application_professional on id_professional
  • idx_application_status on status
  • idx_application_applied_at on applied_at
Relationships:
  • ManyToOneservice_request
  • ManyToOneprofessional_profile
  • OneToOneservice_job

service_job

Active or completed jobs (accepted applications).
ColumnTypeConstraintsDescription
id_jobBIGINTPRIMARY KEY, AUTO_INCREMENTUnique job identifier
agreed_priceDECIMAL(8,2)NOT NULLFinal agreed price
start_dateTIMESTAMPWhen job started
end_dateTIMESTAMPWhen job was completed
notesTEXTAdditional notes (max 2000 chars)
statusVARCHAR(20)NOT NULLCREATED, IN_PROGRESS, COMPLETED, CANCELLED, PAUSED
id_requestBIGINTFOREIGN KEY, NOT NULLReference to service_request
id_applicationBIGINTFOREIGN KEY, NOT NULLReference to job_application
created_byVARCHAR(100)Audit field
created_atTIMESTAMPAudit field
updated_byVARCHAR(100)Audit field
updated_atTIMESTAMPAudit field
Indexes:
  • idx_job_status on status
  • idx_job_request on id_request
  • idx_job_application on id_application
  • idx_job_dates on start_date, end_date
  • idx_job_active on status, start_date
  • idx_job_price on agreed_price
Relationships:
  • ManyToOneservice_request
  • ManyToOnejob_application
  • OneToManyrating

rating

Ratings and reviews after job completion.
ColumnTypeConstraintsDescription
id_ratingBIGINTPRIMARY KEY, AUTO_INCREMENTUnique rating identifier
id_jobBIGINTFOREIGN KEY, NOT NULLReference to service_job
typeVARCHAR(30)NOT NULLCLIENT_TO_PROFESSIONAL, PROFESSIONAL_TO_CLIENT
scoreINTEGERNOT NULLRating score (1-5)
commentVARCHAR(500)Optional comment
rated_atTIMESTAMPWhen rating was given
statusVARCHAR(20)NOT NULLPENDING, PUBLISHED, HIDDEN
created_byVARCHAR(100)Audit field
created_atTIMESTAMPAudit field
updated_byVARCHAR(100)Audit field
updated_atTIMESTAMPAudit field
Indexes:
  • idx_rating_job on id_job
  • idx_rating_type on type
Relationships:
  • ManyToOneservice_job

Audit & Logging Tables

access_log

Tracks user login attempts (successful and failed).
ColumnTypeConstraintsDescription
id_logBIGINTPRIMARY KEY, AUTO_INCREMENTUnique log identifier
accessed_atTIMESTAMPWhen access attempt occurred
source_ipVARCHAR(45)IP address of the request
successBOOLEANNOT NULL, DEFAULT FALSEWhether login was successful
id_userBIGINTFOREIGN KEY, NOT NULLReference to app_user
Indexes:
  • idx_access_log_user on id_user
  • idx_access_log_accessed_at on accessed_at
Relationships:
  • ManyToOneapp_user

Join Tables

professional_category

Many-to-many relationship between professionals and categories.
ColumnTypeConstraintsDescription
id_professionalBIGINTFOREIGN KEY, PRIMARY KEYReference to professional_profile
id_categoryBIGINTFOREIGN KEY, PRIMARY KEYReference to category
Composite Primary Key: (id_professional, id_category)

Cascade Behavior

Several entities have cascade delete configured. Deleting a parent record will cascade to related records:
  • Deleting an AppUser will delete their ProfessionalProfile, ServiceRequests, and AccessLogs
  • Deleting a ServiceRequest will delete all JobApplications and ServiceJobs
  • Deleting a ServiceJob will delete all associated Ratings
  • Deleting a ProfessionalProfile will delete all JobApplications

Auditing

All entities (except AccessLog) extend BaseEntity which provides automatic auditing:
  • created_by: Username who created the record
  • created_at: Timestamp when record was created
  • updated_by: Username who last updated the record
  • updated_at: Timestamp of last update
Auditing is enabled through Spring Data JPA’s @EntityListeners(AuditingEntityListener.class).

Build docs developers (and LLMs) love