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