This project is a template for the company. It contains the basic structure of a Spring project, with the necessary dependencies and configurations to start a new project.
1. Spring Boot 3.1.4
2. Spring Data JPA
3. Spring Security
4. Spring Web
5. Spring starter mail
6. Spring quartz for scheduling tasks(cron jobs)
7. Spring themeleaf for template engine
8 Spring gcp for google cloud platform
9. Spring devtools for hot reload
10. Spring postgresql driver
11. Spring lombok
The project consists of three profiles: dev, uat and prod. Each profile has its own configuration file, which is
located in the resources folder. The configuration files are named as follows: application-{profile}.properties.
By default, the dev profile is active. To change the active profile, you must change the value of the property in the
application.properties file: spring.profiles.active = {profile} or edit your IDE configuration to point to the required profile.
Any configuration that is common to all profiles is located in the application.properties file. The configuration that is specific to each profile is located in the application-{profile}.properties file.
NOTE: Adding a configuration to the `application-{profile}.properties` file will override the configuration in the `application.properties` file.
The project uses PostgreSQL as the database. The database configuration is located in the application-{profile}.properties file.
The database configuration is as follows:
spring.datasource.url=jdbc:postgresql://localhost:5432/{database_name}
spring.datasource.username={username}
spring.datasource.password={password}
spring.datasource.driver-class-name=org.postgresql.Driver
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect
spring.jpa.hibernate.ddl-auto=noneIf your project uses model-database architecture, you can change the
spring.jpa.hibernate.ddl-auto property to create-drop to create the database tables automatically or to update to update the database tables automatically.
With this in place, you don't need to create the tables manually.
The project uses the Gmail SMTP server to send emails. The mail configuration is located in the application-{profile}.properties file.
The mail configuration is as follows:
spring.mail.host=smtp.servicecops.com
spring.mail.port=25
spring.mail.username={username}
spring.mail.password={password}
spring.mail.properties.mail.smtp.auth=true
spring.mail.properties.mail.smtp.starttls.enable=true
spring.mail.default-encoding=UTF-8
spring.mail.properties.protocol=smtp
spring.mail.properties.test-connection=falseThe project uses Google Cloud Platform to store files. The configuration is located in the application-{profile}.properties file.
There is a bucket already created in the GCP which is used by default, you are encouraged to create your own folder in this bucket where you can store your files.
The configuration is as follows:
spring.cloud.gcp.storage.bucket={bucket_name}
spring.cloud.gcp.credentials.location={path_to_json_file}
gcp.bucket.name={name_of_your_bucket} # Please add the same name as in the file above.This is the port you want your server to run on. The default port is 8080. You can change it in the application-{profile}.properties file. Or you can change it in the application.properties file and delete it from the profile files.
Doing this will make the port the same for all profiles.
server.port=8080This is the version of your application. You can change it in the application-{profile}.properties file. Or you can change it in the application.properties file and delete it from the profile files.
Doing this will make the version the same for all profiles.
app.version=SC-PROJ-20230928001This version is divided into four parts:
- SC - The initials of the company.
- PROJ - The initials of the project.
- 20230928 - The date of the project creation.
- 001 - The number of the release.
This will always help you to truck which version has which issue especially in the uat.
This projects provides first-hand support for moonlight as if it is part of the other dependencies.
In the pom.xml you will find the following:-
<dependency>
<groupId>com.jmsoft</groupId>
<artifactId>Moonlight</artifactId>
<version>1.0</version>
</dependency>Which implies, you are free to start using moonlight in your project as you see fit.
The project ships in with the default authentication system based on OAuth1.0 with JWT as the transfer protocol. No sessions are maintained in the app.
All endpoints to this project are POST requests unless otherwise.
All endpoints point to the central controller that is located in the api package.
All endpoints as JSON and their definition is as follows:-
{
"SERVICE": "{SERVICE_NAME}",
"ACTION": "{ACTION_NAME}",
// the rest of the request data in the format of:-
"KEY": "VALUE" // this can be as many as you want.
}If the endpoint supports searching, then the object SEARCH is required in the request.
{
"SERVICE": "{SERVICE_NAME}",
"ACTION": "{ACTION_NAME}",
"SEARCH": {
"KEY": "VALUE" // this can be as many as you want.
},
// the rest of the request data in the format of:-
"KEY": "VALUE" // this can be as many as you want.
}The search object can carry an empty object but MUST be defined.
This architecture does not support Multipart data however, all files must sent as base64 encodings strings
The following is the structure of the project.
-
utils This holds all the helpers for the project. These include file uploads, mail services, request responses, AES utilities and many more.
-
models i. enums ii. database(actual models to the database) iii. customs(custom field mappings from the database) iv. jpahelpers
-
api
-
config
-
services (where all your kungfu happens)
-
repositories
All repository must/should extend from JetRepository not JpaRepository as JetRepository itself extends JpaRepository. This custom repository, on top of all the other features from other repositories, gives you an opportunity to
refresh your data from the database directly. This case is handy when you have data that is auto-generated at
the database level through say triggers or something like that.
Usage:
import com.servicecops.project.models.jpahelpers.repository.JetRepository;
// declaration.
@Repository
public interface AccountsRepository extends JetRepository<AccountsModel, Long> {
}
//....
// auto-wiring it
private final AccountsRepository accountsRepository;
// saving
AccountsModal saved = accountsRepository.save(someModal);
accountsRepository.refresh();
// here accessing 'saved' from here onwards will consist of fresh data from the db including the auto-generated one.All your procedures should leave in the folder called procedures as independent SQL files.
This folder should reside in the root of your project, and most probably, you will find it created for you.
If you have some stored procedures that you want to call from the Java Side,
Create for them a method in your repository like this.
@Repository
public interface ReadingCycleRepository extends JetRepository<AuthorityReadingCycleModel, Long> {
@Query(value = "select * from cycle_engine_front()", nativeQuery = true)
String runCycle();
@Query(value = "SELECT * FROM billing_engine_front(:customerNumber, :billingCycle)", nativeQuery = true)
BillModel billACustomerByCycle(@Param("customerNumber") String customer_number, @Param("billingCycle") String cycle);
}In the above, my first procedure is called cycle_engine_front() and it takes no parameters but returns a string.
The second procedure is called billing_engine_front(:customerNumber, :billingCycle) which takes on parameters and returns database model/table called BillModel.
If your procedures raise some exceptions, there is currently no directly way of accessing these exceptions from the java side, HOWEVER, there is a simple hack to handle this.
But first,
If your functions/stored procedures are nested and you're raising exceptions in internal functions that are called by a major procedure/function, in that major function, first catch all the exceptions as follows:-
This is not a very correct procedure, but you should get the point.
create or replace function my_func(param1 varchar, param2 varchar) returns any
language plpgsql
as
$$
BEGIN
begin
-- put all your logic here
-- including calling internal procedures that are raising exceptions
exception when others then
RAISE EXCEPTION ' % ', SQLERRM;
end;
return any;
END;
$$;
As you can see, my major function catches all the exceptions raised by child functions and re-raises them all.
Back to your Java side, this can now be handled like:-
try {
// attempting to call a procedure that actually raises the exception
returnData = readingcycleRepository
.billACustomerByCycle(customerNumber, billingCycle);
} catch (Exception e){
// this is where we hack the solution to the above problem.
String error= e.getCause().getCause().getLocalizedMessage();
String[] str = StringUtils.split(error, "\n");
String cleanError = str[0].trim();
throw new IllegalStateException(cleanError);
}