diff --git a/.env b/.env
index 76d54b3..f1f6729 100644
--- a/.env
+++ b/.env
@@ -1,4 +1,6 @@
# Database settings
MYSQL_USER=root
MYSQL_PASSWORD=root
-MYSQL_DB=Splitora
\ No newline at end of file
+MYSQL_DB=Splitora
+
+SPRING_LIQUIBASE_CONTEXTS=dev
\ No newline at end of file
diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml
index b5334bf..6c7415f 100644
--- a/.github/workflows/deploy.yml
+++ b/.github/workflows/deploy.yml
@@ -31,7 +31,10 @@ jobs:
- name: Build Docker Image
run: |
- docker build -t ${{ secrets.DOCKER_HUB_USERNAME }}/splitora-app:1.0 .
+ docker build \
+ --build-arg SPRING_LIQUIBASE_CONTEXTS=prod \
+ --build-arg SPRING_LIQUIBASE_CHANGELOG=classpath:db/changelog/db.changelog-master-prod.yaml
+ -t ${{ secrets.DOCKER_HUB_USERNAME }}/splitora-app:1.0 .
- name: Push Docker Image to Docker Hub
run: |
diff --git a/Dockerfile b/Dockerfile
index 8a0beb3..0ac7790 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -7,6 +7,9 @@ WORKDIR /app
# Copy the jar file into the container at /app
COPY target/*.jar app.jar
+ARG SPRING_LIQUIBASE_CONTEXTS
+ENV SPRING_LIQUIBASE_CONTEXTS=${SPRING_LIQUIBASE_CONTEXTS}
+
# Expose the port the app runs in
EXPOSE 8081
diff --git a/docker-compose.yml b/docker-compose.yml
index a1e75a8..407058c 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -27,6 +27,7 @@ services:
SPRING_DATASOURCE_URL: jdbc:mysql://mysql:3306/${MYSQL_DB} # access the mysql container at the port 3306
SPRING_DATASOURCE_USERNAME: ${MYSQL_USER}
SPRING_DATASOURCE_PASSWORD: ${MYSQL_PASSWORD}
+ SPRING_LIQUIBASE_CONTEXTS: dev
networks:
- splitora-db-network
diff --git a/pom.xml b/pom.xml
index 8dcaf1e..4c03104 100644
--- a/pom.xml
+++ b/pom.xml
@@ -119,6 +119,13 @@
springdoc-openapi-starter-webmvc-ui
2.5.0
+
+
+
+ org.liquibase
+ liquibase-core
+ 4.31.0
+
diff --git a/src/main/java/com/satwik/splitora/exception/ApiExceptionHandler.java b/src/main/java/com/satwik/splitora/exception/ApiExceptionHandler.java
index 915fe7c..422c3e6 100644
--- a/src/main/java/com/satwik/splitora/exception/ApiExceptionHandler.java
+++ b/src/main/java/com/satwik/splitora/exception/ApiExceptionHandler.java
@@ -18,7 +18,7 @@ public class ApiExceptionHandler {
@ExceptionHandler(BadRequestException.class)
public ResponseEntity handleBadRequestException(RuntimeException ex) {
log.info("Runtime exception occurred: ", ex);
- ErrorResponseModel errorResponse = ResponseUtil.error("Bad Request", HttpStatus.BAD_REQUEST, new ErrorDetails(
+ ErrorResponseModel errorResponse = ResponseUtil.error(ex.getMessage(), HttpStatus.BAD_REQUEST, new ErrorDetails(
ErrorCode.INVALID_REQUEST.getCode(),
ErrorCode.INVALID_REQUEST.getMessage()
));
@@ -39,7 +39,7 @@ public ResponseEntity handleRefreshTokenInvalidException(Ref
@ExceptionHandler(DataNotFoundException.class)
public ResponseEntity handleDataNotFoundException(DataNotFoundException ex) {
log.info("DataNotFoundException occurred: ", ex);
- ErrorResponseModel errorResponse = ResponseUtil.error("Data Not Found", HttpStatus.NOT_FOUND, new ErrorDetails(
+ ErrorResponseModel errorResponse = ResponseUtil.error(ex.getMessage(), HttpStatus.NOT_FOUND, new ErrorDetails(
ErrorCode.ENTITY_NOT_FOUND.getCode(),
ErrorCode.ENTITY_NOT_FOUND.getMessage()
));
diff --git a/src/main/java/com/satwik/splitora/persistence/dto/expense/ExpenseDTO.java b/src/main/java/com/satwik/splitora/persistence/dto/expense/ExpenseDTO.java
index 4662d00..23f872c 100644
--- a/src/main/java/com/satwik/splitora/persistence/dto/expense/ExpenseDTO.java
+++ b/src/main/java/com/satwik/splitora/persistence/dto/expense/ExpenseDTO.java
@@ -1,5 +1,6 @@
package com.satwik.splitora.persistence.dto.expense;
+import com.fasterxml.jackson.annotation.JsonFormat;
import com.satwik.splitora.persistence.dto.user.OwerDTO;
import jakarta.validation.constraints.NotNull;
import lombok.*;
@@ -24,6 +25,7 @@ public class ExpenseDTO {
@NotNull
private String description;
+ @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss")
private LocalDateTime date;
private List owers;
diff --git a/src/main/java/com/satwik/splitora/persistence/entities/User.java b/src/main/java/com/satwik/splitora/persistence/entities/User.java
index a44ef7f..2353e0f 100644
--- a/src/main/java/com/satwik/splitora/persistence/entities/User.java
+++ b/src/main/java/com/satwik/splitora/persistence/entities/User.java
@@ -34,7 +34,7 @@ public class User extends BaseEntity {
private String password;
@Enumerated(EnumType.STRING)
- @Column(name = "registrationMethod")
+ @Column(name = "registration_method")
private RegistrationMethod registrationMethod;
@Enumerated(EnumType.STRING)
diff --git a/src/main/java/com/satwik/splitora/repository/UserRepository.java b/src/main/java/com/satwik/splitora/repository/UserRepository.java
index cef2711..50106fc 100644
--- a/src/main/java/com/satwik/splitora/repository/UserRepository.java
+++ b/src/main/java/com/satwik/splitora/repository/UserRepository.java
@@ -11,4 +11,6 @@
public interface UserRepository extends JpaRepository {
Optional findByEmail(String email);
+
+ boolean existsByEmail(String email);
}
diff --git a/src/main/java/com/satwik/splitora/service/implementations/GroupServiceImpl.java b/src/main/java/com/satwik/splitora/service/implementations/GroupServiceImpl.java
index 7221ee4..2313b1e 100644
--- a/src/main/java/com/satwik/splitora/service/implementations/GroupServiceImpl.java
+++ b/src/main/java/com/satwik/splitora/service/implementations/GroupServiceImpl.java
@@ -91,6 +91,7 @@ public List findMembers(UUID groupId) {
List userDTOS = new ArrayList<>();
for (GroupMembers groupMembers : groupMembersList) {
UserDTO userDTO = new UserDTO();
+ userDTO.setUserId(groupMembers.getMember().getId());
userDTO.setEmail(groupMembers.getMember().getEmail());
userDTO.setUsername(groupMembers.getMember().getUsername());
userDTO.setPhone(new PhoneDTO(groupMembers.getMember().getCountryCode(), groupMembers.getMember().getPhoneNumber()));
diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties
deleted file mode 100644
index 60b7ffa..0000000
--- a/src/main/resources/application.properties
+++ /dev/null
@@ -1,36 +0,0 @@
-# connection of spring application with database
-
-spring.datasource.url=jdbc:mysql://localhost:3306/Splitora
-spring.datasource.username=root
-spring.datasource.password=root
-
-spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
-
-# Hibernate settings: set here configurations for Hibernate
-# Show SQL
-#spring.jpa.show-sql=true
-# Hibernate ddl auto (create, create-drop, validate, update)
-spring.jpa.hibernate.ddl-auto=update
-# Naming strategy
-spring.jpa.hibernate.naming-strategy=org.hibernate.cfg.ImprovedNamingStrategy
-
-server.port=8081
-
-# JWT security--
-jwt.access.secretKey=SuP9ErKeY
-jwt.refresh.secretKey=wUP89ErKie
-jwt.access.expirationTimeInMinutes=150
-jwt.refresh.expirationTimeInMinutes=2000
-
-# Google authentication properties
-spring.security.oauth2.client.registration.google.client-id=abc123
-spring.security.oauth2.client.registration.google.client-secret=good123
-spring.security.oauth2.client.registration.google.scope=openid,email,profile
-spring.security.oauth2.client.registration.google.redirect-uri=http://localhost:3000/callback
-spring.security.oauth2.client.provider.google.authorization-uri=https://accounts.google.com/o/oauth2/auth
-spring.security.oauth2.client.provider.google.token-uri=https://oauth2.googleapis.com/token
-spring.security.oauth2.client.provider.google.user-info-uri=https://www.googleapis.com/oauth2/v3/userinfo
-spring.security.oauth2.client.provider.google.user-name-attribute=name
-
-# file path for the report
-my.reportFilePath=/home/ongraph/Downloads/
\ No newline at end of file
diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml
new file mode 100644
index 0000000..ae5cec3
--- /dev/null
+++ b/src/main/resources/application.yml
@@ -0,0 +1,67 @@
+# Connection of spring application with database
+spring:
+ datasource:
+ url: jdbc:mysql://localhost:3306/Splitora
+ username: root
+ password: root
+ driver-class-name: com.mysql.cj.jdbc.Driver
+
+ jpa:
+ hibernate:
+ ddl-auto: none
+ naming-strategy: org.hibernate.cfg.ImprovedNamingStrategy
+
+ liquibase:
+ enabled: true
+ change-log: classpath:db/changelog/db.changelog-master-dev.yaml
+ contexts: ${SPRING_LIQUIBASE_CONTEXTS:dev}
+ # show-sql: true # Uncomment to enable SQL logging
+
+ security:
+ oauth2:
+ client:
+ registration:
+ google:
+ client-id: abc123
+ client-secret: good123
+ scope:
+ - openid
+ - email
+ - profile
+ redirect-uri: http://localhost:3000/callback
+ provider:
+ google:
+ authorization-uri: https://accounts.google.com/o/oauth2/auth
+ token-uri: https://oauth2.googleapis.com/token
+ user-info-uri: https://www.googleapis.com/oauth2/v3/userinfo
+ user-name-attribute: name
+
+server:
+ port: 8081
+
+# JWT security
+jwt:
+ access:
+ secretKey: SuP9ErKeY
+ expirationTimeInMinutes: 150
+ refresh:
+ secretKey: wUP89ErKie
+ expirationTimeInMinutes: 2000
+
+# File path for the report
+my:
+ reportFilePath: /home/ongraph/Downloads/
+
+app:
+ initial-users:
+ super-admin:
+ username: ${SUPERADMIN_USERNAME}
+ password: ${SUPERADMIN_PASSWORD}
+ email: ${SUPERADMIN_EMAIL}
+ regular-users:
+ - username: ${USER1_USERNAME}
+ password: ${USER1_PASSWORD}
+ email: ${USER1_EMAIL}
+ - username: ${USER2_USERNAME}
+ password: ${USER2_PASSWORD}
+ email: ${USER2_EMAIL}
\ No newline at end of file
diff --git a/src/main/resources/db/changelog/data/seed-groups.yaml b/src/main/resources/db/changelog/data/seed-groups.yaml
new file mode 100644
index 0000000..bb09f90
--- /dev/null
+++ b/src/main/resources/db/changelog/data/seed-groups.yaml
@@ -0,0 +1,25 @@
+databaseChangeLog:
+ - changeSet:
+ id: 2025-05-31-load-groups
+ author: splitora
+ context: dev
+ changes:
+ - sql:
+ splitStatements: false
+ stripComments: true
+ sql: |
+ INSERT IGNORE INTO group_table (
+ id, created_by, created_on, modified_by, modified_on, default_group, group_name, user_id
+ ) VALUES
+ (
+ UNHEX('a1b2c3d4e5f60123456789abcdef0001'), NULL, '2025-05-26 13:24:27.589321', NULL, '2025-05-26 13:24:27.589321',
+ 1, 'Non Grouped Expenses', UNHEX('3719917506b14643afc7b8815fdc81cf')
+ ),
+ (
+ UNHEX('a1b2c3d4e5f60123456789abcdef0002'), NULL, '2025-05-26 13:24:27.596714', NULL, '2025-05-26 13:24:27.596714',
+ 1, 'Non Grouped Expenses', UNHEX('5790c07ad90e4df6a062e6465f658127')
+ ),
+ (
+ UNHEX('a1b2c3d4e5f60123456789abcdef0003'), NULL, '2025-05-26 13:24:27.601860', NULL, '2025-05-26 13:24:27.601864',
+ 1, 'Non Grouped Expenses', UNHEX('784ad2e326344ba58c5028c8ee56bcd8')
+ );
diff --git a/src/main/resources/db/changelog/data/seed-users.yaml b/src/main/resources/db/changelog/data/seed-users.yaml
new file mode 100644
index 0000000..ab8074e
--- /dev/null
+++ b/src/main/resources/db/changelog/data/seed-users.yaml
@@ -0,0 +1,30 @@
+databaseChangeLog:
+ - changeSet:
+ id: 2025-05-31-load-users
+ author: splitora
+ context: dev
+ changes:
+ - sql:
+ splitStatements: false
+ stripComments: true
+ sql: |
+ INSERT IGNORE INTO user (
+ id, created_by, created_on, modified_by, modified_on,
+ username, email, phone_country_code, phone_number,
+ password, registration_method, user_role
+ ) VALUES
+ (
+ UNHEX('3719917506b14643afc7b8815fdc81cf'), NULL, '2025-05-26 13:24:27', NULL, '2025-05-26 13:24:27',
+ 'nakul_test_90', 'nakul@example.com', '+91', '0',
+ '$2a$10$txo1PsQ7M.2uLv/HO.S0TuRHWJPvV8X2oO4gEh/QBNfTX8KIHEuma', 'NORMAL', 'USER'
+ ),
+ (
+ UNHEX('5790c07ad90e4df6a062e6465f658127'), NULL, '2025-05-26 13:24:27', NULL, '2025-05-26 13:24:27',
+ 'superadmin', 'superadmin@example.com', '+91', '99',
+ '$2a$10$ndZUrGTuqXw1buq9/MF34eM1XnrnFzwx9227xCGockloaFq.zPb8K', 'NORMAL', 'ADMIN'
+ ),
+ (
+ UNHEX('784ad2e326344ba58c5028c8ee56bcd8'), NULL, '2025-05-26 13:24:27', NULL, '2025-05-26 13:24:27',
+ 'gaurav_test_22', 'gaurav@example.com', '+91', '1',
+ '$2a$10$ILSK/k88Rd6SUbiMBHg/IOhuWGk3N5WqRDLz3gDL0.ne.6nANMeaG', 'NORMAL', 'USER'
+ ) ;
\ No newline at end of file
diff --git a/src/main/resources/db/changelog/db.changelog-master-dev.yaml b/src/main/resources/db/changelog/db.changelog-master-dev.yaml
new file mode 100644
index 0000000..5d3aef8
--- /dev/null
+++ b/src/main/resources/db/changelog/db.changelog-master-dev.yaml
@@ -0,0 +1,22 @@
+databaseChangeLog:
+ - include:
+ file: db/changelog/schema/user-create.yaml
+ context: dev
+ - include:
+ file: db/changelog/schema/group-create.yaml
+ context: dev
+ - include:
+ file: db/changelog/schema/groupmembers-create.yaml
+ context: dev
+ - include:
+ file: db/changelog/schema/expense-create.yaml
+ context: dev
+ - include:
+ file: db/changelog/schema/expenseshare-create.yaml
+ context: dev
+ - include:
+ file: db/changelog/data/seed-users.yaml
+ context: dev
+ - include:
+ file: db/changelog/data/seed-groups.yaml
+ context: dev
\ No newline at end of file
diff --git a/src/main/resources/db/changelog/db.changelog-master-prod.yaml b/src/main/resources/db/changelog/db.changelog-master-prod.yaml
new file mode 100644
index 0000000..ad9149d
--- /dev/null
+++ b/src/main/resources/db/changelog/db.changelog-master-prod.yaml
@@ -0,0 +1,22 @@
+databaseChangeLog:
+ - include:
+ file: db/changelog/schema/user-create.yaml
+ context: prod
+ - include:
+ file: db/changelog/schema/group-create.yaml
+ context: prod
+ - include:
+ file: db/changelog/schema/groupmembers-create.yaml
+ context: prod
+ - include:
+ file: db/changelog/schema/expense-create.yaml
+ context: prod
+ - include:
+ file: db/changelog/schema/expenseshare-create.yaml
+ context: prod
+ - include:
+ file: file:/etc/secrets/seed-users.yaml
+ context: prod
+ - include:
+ file: file:/etc/secrets/seed-groups.yaml
+ context: prod
\ No newline at end of file
diff --git a/src/main/resources/db/changelog/schema/expense-create.yaml b/src/main/resources/db/changelog/schema/expense-create.yaml
new file mode 100644
index 0000000..948689c
--- /dev/null
+++ b/src/main/resources/db/changelog/schema/expense-create.yaml
@@ -0,0 +1,45 @@
+databaseChangeLog:
+ - changeSet:
+ id: create-expense-table
+ author: splitora
+ changes:
+ - createTable:
+ tableName: expense
+ columns:
+ - column:
+ name: id
+ type: BINARY(16)
+ constraints:
+ primaryKey: true
+ - column:
+ name: created_by
+ type: BINARY(16)
+ - column:
+ name: created_on
+ type: TIMESTAMP
+ defaultValueComputed: CURRENT_TIMESTAMP
+ - column:
+ name: modified_by
+ type: BINARY(16)
+ - column:
+ name: modified_on
+ type: TIMESTAMP
+ defaultValueComputed: CURRENT_TIMESTAMP
+ - column:
+ name: group_id
+ type: BINARY(16)
+ constraints:
+ foreignKeyName: fk_expense_group
+ references: group_table(id)
+ - column:
+ name: payer_id
+ type: BINARY(16)
+ constraints:
+ foreignKeyName: fk_expense_payer
+ references: user(id)
+ - column:
+ name: amount
+ type: DOUBLE
+ - column:
+ name: description
+ type: VARCHAR(255)
\ No newline at end of file
diff --git a/src/main/resources/db/changelog/schema/expenseshare-create.yaml b/src/main/resources/db/changelog/schema/expenseshare-create.yaml
new file mode 100644
index 0000000..f652eea
--- /dev/null
+++ b/src/main/resources/db/changelog/schema/expenseshare-create.yaml
@@ -0,0 +1,42 @@
+databaseChangeLog:
+ - changeSet:
+ id: create-expenseshare-table
+ author: splitora
+ changes:
+ - createTable:
+ tableName: expense_share
+ columns:
+ - column:
+ name: id
+ type: BINARY(16)
+ constraints:
+ primaryKey: true
+ - column:
+ name: created_by
+ type: BINARY(16)
+ - column:
+ name: created_on
+ type: TIMESTAMP
+ defaultValueComputed: CURRENT_TIMESTAMP
+ - column:
+ name: modified_by
+ type: BINARY(16)
+ - column:
+ name: modified_on
+ type: TIMESTAMP
+ defaultValueComputed: CURRENT_TIMESTAMP
+ - column:
+ name: expense_id
+ type: BINARY(16)
+ constraints:
+ foreignKeyName: fk_expenseshare_expense
+ references: expense(id)
+ - column:
+ name: user_id
+ type: BINARY(16)
+ constraints:
+ foreignKeyName: fk_expenseshare_user
+ references: user(id)
+ - column:
+ name: shared_amount
+ type: DOUBLE
\ No newline at end of file
diff --git a/src/main/resources/db/changelog/schema/group-create.yaml b/src/main/resources/db/changelog/schema/group-create.yaml
new file mode 100644
index 0000000..e39207e
--- /dev/null
+++ b/src/main/resources/db/changelog/schema/group-create.yaml
@@ -0,0 +1,39 @@
+databaseChangeLog:
+ - changeSet:
+ id: create-group-table
+ author: splitora
+ changes:
+ - createTable:
+ tableName: group_table
+ columns:
+ - column:
+ name: id
+ type: BINARY(16)
+ constraints:
+ primaryKey: true
+ - column:
+ name: created_by
+ type: BINARY(16)
+ - column:
+ name: created_on
+ type: TIMESTAMP
+ defaultValueComputed: CURRENT_TIMESTAMP
+ - column:
+ name: modified_by
+ type: BINARY(16)
+ - column:
+ name: modified_on
+ type: TIMESTAMP
+ defaultValueComputed: CURRENT_TIMESTAMP
+ - column:
+ name: group_name
+ type: VARCHAR(255)
+ - column:
+ name: default_group
+ type: BIT
+ - column:
+ name: user_id
+ type: BINARY(16)
+ constraints:
+ foreignKeyName: fk_group_user
+ references: user(id)
\ No newline at end of file
diff --git a/src/main/resources/db/changelog/schema/groupmembers-create.yaml b/src/main/resources/db/changelog/schema/groupmembers-create.yaml
new file mode 100644
index 0000000..f4f87f5
--- /dev/null
+++ b/src/main/resources/db/changelog/schema/groupmembers-create.yaml
@@ -0,0 +1,39 @@
+databaseChangeLog:
+ - changeSet:
+ id: create-groupmembers-table
+ author: splitora
+ changes:
+ - createTable:
+ tableName: group_members
+ columns:
+ - column:
+ name: id
+ type: BINARY(16)
+ constraints:
+ primaryKey: true
+ - column:
+ name: created_by
+ type: BINARY(16)
+ - column:
+ name: created_on
+ type: TIMESTAMP
+ defaultValueComputed: CURRENT_TIMESTAMP
+ - column:
+ name: modified_by
+ type: BINARY(16)
+ - column:
+ name: modified_on
+ type: TIMESTAMP
+ defaultValueComputed: CURRENT_TIMESTAMP
+ - column:
+ name: group_id
+ type: BINARY(16)
+ constraints:
+ foreignKeyName: fk_groupmembers_group
+ references: group_table(id)
+ - column:
+ name: member_id
+ type: BINARY(16)
+ constraints:
+ foreignKeyName: fk_groupmembers_member
+ references: user(id)
\ No newline at end of file
diff --git a/src/main/resources/db/changelog/schema/user-create.yaml b/src/main/resources/db/changelog/schema/user-create.yaml
new file mode 100644
index 0000000..36af742
--- /dev/null
+++ b/src/main/resources/db/changelog/schema/user-create.yaml
@@ -0,0 +1,48 @@
+databaseChangeLog:
+ - changeSet:
+ id: create-user-table
+ author: splitora
+ changes:
+ - createTable:
+ tableName: user
+ columns:
+ - column:
+ name: id
+ type: BINARY(16)
+ constraints:
+ primaryKey: true
+ - column:
+ name: created_by
+ type: BINARY(16)
+ - column:
+ name: created_on
+ type: TIMESTAMP
+ defaultValueComputed: CURRENT_TIMESTAMP
+ - column:
+ name: modified_by
+ type: BINARY(16)
+ - column:
+ name: modified_on
+ type: TIMESTAMP
+ defaultValueComputed: CURRENT_TIMESTAMP
+ - column:
+ name: username
+ type: VARCHAR(255)
+ - column:
+ name: email
+ type: VARCHAR(255)
+ - column:
+ name: phone_country_code
+ type: VARCHAR(4)
+ - column:
+ name: phone_number
+ type: BIGINT
+ - column:
+ name: password
+ type: VARCHAR(255)
+ - column:
+ name: registration_method
+ type: "ENUM('NORMAL','GOOGLE','MICROSOFT')"
+ - column:
+ name: user_role
+ type: "ENUM('USER','ADMIN','TESTER')"
\ No newline at end of file