diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
new file mode 100644
index 0000000..dd84ea7
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -0,0 +1,38 @@
+---
+name: Bug report
+about: Create a report to help us improve
+title: ''
+labels: ''
+assignees: ''
+
+---
+
+**Describe the bug**
+A clear and concise description of what the bug is.
+
+**To Reproduce**
+Steps to reproduce the behavior:
+1. Go to '...'
+2. Click on '....'
+3. Scroll down to '....'
+4. See error
+
+**Expected behavior**
+A clear and concise description of what you expected to happen.
+
+**Screenshots**
+If applicable, add screenshots to help explain your problem.
+
+**Desktop (please complete the following information):**
+ - OS: [e.g. iOS]
+ - Browser [e.g. chrome, safari]
+ - Version [e.g. 22]
+
+**Smartphone (please complete the following information):**
+ - Device: [e.g. iPhone6]
+ - OS: [e.g. iOS8.1]
+ - Browser [e.g. stock browser, safari]
+ - Version [e.g. 22]
+
+**Additional context**
+Add any other context about the problem here.
diff --git a/.github/ISSUE_TEMPLATE/custom.md b/.github/ISSUE_TEMPLATE/custom.md
new file mode 100644
index 0000000..48d5f81
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/custom.md
@@ -0,0 +1,10 @@
+---
+name: Custom issue template
+about: Describe this issue template's purpose here.
+title: ''
+labels: ''
+assignees: ''
+
+---
+
+
diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md
new file mode 100644
index 0000000..bbcbbe7
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/feature_request.md
@@ -0,0 +1,20 @@
+---
+name: Feature request
+about: Suggest an idea for this project
+title: ''
+labels: ''
+assignees: ''
+
+---
+
+**Is your feature request related to a problem? Please describe.**
+A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
+
+**Describe the solution you'd like**
+A clear and concise description of what you want to happen.
+
+**Describe alternatives you've considered**
+A clear and concise description of any alternative solutions or features you've considered.
+
+**Additional context**
+Add any other context or screenshots about the feature request here.
diff --git a/.github/workflows/build-and-publish-develop.yaml b/.github/workflows/build-and-publish-develop.yaml
new file mode 100644
index 0000000..92d3ea9
--- /dev/null
+++ b/.github/workflows/build-and-publish-develop.yaml
@@ -0,0 +1,66 @@
+name: SNAPSHOT - Build and Publish Maven Artifacts
+
+on:
+ workflow_dispatch:
+ push:
+ branches:
+ - develop
+
+env:
+ NEXUS_USERNAME: 'edward'
+ NEXUS_PASSWORD: ${{ secrets.NEXUS_PASSWORD }}
+ MAVEN_PUBLIC_REPOSITORY_URL: ${{ secrets.MAVEN_PUBLIC_REPOSITORY_URL }}
+ SNAPSHOT_DEPLOYMENT_REPOSITORY_URL: ${{ secrets.SNAPSHOT_DEPLOYMENT_REPOSITORY_URL }}
+ RELEASE_DEPLOYMENT_REPOSITORY_URL: ${{ secrets.RELEASE_DEPLOYMENT_REPOSITORY_URL }}
+
+jobs:
+ build-java:
+ runs-on: ubuntu-latest
+ permissions:
+ id-token: write
+ contents: write
+ packages: write
+ name: Build Java Package and Publish
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+ - uses: actions/setup-java@v4.2.2
+ if: ${{ hashFiles('**/pom.xml') }}
+ with:
+ java-version: 17
+ distribution: zulu
+ cache: 'maven'
+ - name: maven-settings-xml-action
+ uses: whelk-io/maven-settings-xml-action@v22
+ with:
+ profiles: >
+ [{
+ "id": "homelab",
+ "properties": {
+ "altSnapshotDeploymentRepository": "nexus-snapshot::${env.SNAPSHOT_DEPLOYMENT_REPOSITORY_URL}",
+ "altReleaseDeploymentRepository": "nexus-release::${env.RELEASE_DEPLOYMENT_REPOSITORY_URL}"
+ }
+ }]
+ repositories: >
+ [{
+ "id": "maven-public",
+ "url": "${env.MAVEN_PUBLIC_REPOSITORY_URL}",
+ "snapshots": {
+ "enabled": "true"
+ }
+ }]
+ servers: >
+ [{
+ "id": "nexus-snapshot",
+ "username": "${env.NEXUS_USERNAME}",
+ "password": "${env.NEXUS_PASSWORD}"
+ },
+ {
+ "id": "nexus-release",
+ "username": "${env.NEXUS_USERNAME}",
+ "password": "${env.NEXUS_PASSWORD}"
+ }]
+ - name: Maven Publish
+ run: |
+ mvn -B deploy -P homelab
\ No newline at end of file
diff --git a/.github/workflows/build-and-publish-release.yml b/.github/workflows/build-and-publish-release.yml
new file mode 100644
index 0000000..b318b02
--- /dev/null
+++ b/.github/workflows/build-and-publish-release.yml
@@ -0,0 +1,80 @@
+name: RELEASE - Build and Publish Maven Artifacts
+
+on:
+ workflow_dispatch:
+ push:
+ branches:
+ - main
+
+env:
+ NEXUS_USERNAME: 'edward'
+ NEXUS_PASSWORD: ${{ secrets.NEXUS_PASSWORD }}
+ MAVEN_PUBLIC_REPOSITORY_URL: ${{ secrets.MAVEN_PUBLIC_REPOSITORY_URL }}
+ SNAPSHOT_DEPLOYMENT_REPOSITORY_URL: ${{ secrets.SNAPSHOT_DEPLOYMENT_REPOSITORY_URL }}
+ RELEASE_DEPLOYMENT_REPOSITORY_URL: ${{ secrets.RELEASE_DEPLOYMENT_REPOSITORY_URL }}
+
+jobs:
+ build-java:
+ runs-on: ubuntu-latest
+ permissions:
+ id-token: write
+ contents: write
+ packages: write
+ name: Build Java Package and Publish
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+ - uses: actions/setup-java@v4.2.2
+ if: ${{ hashFiles('**/pom.xml') }}
+ with:
+ java-version: 17
+ distribution: zulu
+ cache: 'maven'
+ - name: maven-settings-xml-action
+ uses: whelk-io/maven-settings-xml-action@v22
+ with:
+ profiles: >
+ [{
+ "id": "homelab",
+ "properties": {
+ "altSnapshotDeploymentRepository": "nexus-snapshot::${env.SNAPSHOT_DEPLOYMENT_REPOSITORY_URL}",
+ "altReleaseDeploymentRepository": "nexus-release::${env.RELEASE_DEPLOYMENT_REPOSITORY_URL}"
+ }
+ }]
+ repositories: >
+ [{
+ "id": "maven-public",
+ "url": "${env.MAVEN_PUBLIC_REPOSITORY_URL}",
+ "snapshots": {
+ "enabled": "true"
+ }
+ }]
+ servers: >
+ [{
+ "id": "nexus-snapshot",
+ "username": "${env.NEXUS_USERNAME}",
+ "password": "${env.NEXUS_PASSWORD}"
+ },
+ {
+ "id": "nexus-release",
+ "username": "${env.NEXUS_USERNAME}",
+ "password": "${env.NEXUS_PASSWORD}"
+ }]
+ - name: Config Git
+ run: |
+ git config --global user.email "edward@cheng.sydney"
+ git config --global user.name "3dwardch3ng"
+ git config --global core.autocrlf input
+ - name: Start release
+ run: |
+ mvn gitflow:release-start -B -DpushRemote=true -DallowSnapshots=true -P homelab
+ - name: Maven Publish
+ run: |
+ mvn -B deploy -P homelab
+ - name: Finish release
+ env:
+ GITHUB_ACTOR: 3dwardch3ng
+ GITHUB_TOKEN: ${{ secrets.GH_TOKEN }}
+ run: |
+ mvn gitflow:release-finish -B -DpushRemote=true -DallowSnapshots=true -P homelab
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index 524f096..991bba7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -22,3 +22,5 @@
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
replay_pid*
+
+/.idea/
\ No newline at end of file
diff --git a/.mvn/settings.xml b/.mvn/settings.xml
new file mode 100644
index 0000000..4c81c02
--- /dev/null
+++ b/.mvn/settings.xml
@@ -0,0 +1,65 @@
+
+
+
+ homelab
+
+ nexus-snapshot::${env.SNAPSHOT_DEPLOYMENT_REPOSITORY_URL}
+ nexus-release::${env.RELEASE_DEPLOYMENT_REPOSITORY_URL}
+
+
+
+ nexus-snapshot
+ Home Lab Nexus Snapshot
+
+ false
+
+
+ true
+ always
+ warn
+
+ ${env.SNAPSHOT_DEPLOYMENT_REPOSITORY_URL}
+ default
+
+
+ nexus-release
+ Home Lab Nexus Release
+
+ true
+ always
+ warn
+
+
+ false
+
+ ${env.RELEASE_DEPLOYMENT_REPOSITORY_URL}
+ default
+
+
+
+
+ nexus-central
+ Home Lab Nexus Maven Central
+
+ true
+ warn
+
+ ${env.NEXUS_CENTRAL_REPOSITORY_URL}
+ default
+
+
+
+
+
+
+ nexus-snapshot
+ ${env.NEXUS_USERNAME}
+ ${env.NEXUS_PASSWORD}
+
+
+ nexus-release
+ ${env.NEXUS_USERNAME}
+ ${env.NEXUS_PASSWORD}
+
+
+
\ No newline at end of file
diff --git a/CODEOWNERS b/CODEOWNERS
new file mode 100644
index 0000000..4b66db0
--- /dev/null
+++ b/CODEOWNERS
@@ -0,0 +1,3 @@
+# Repository CODEOWNERS
+
+@3dwardCh3nG
\ No newline at end of file
diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
new file mode 100644
index 0000000..e964f15
--- /dev/null
+++ b/CODE_OF_CONDUCT.md
@@ -0,0 +1,128 @@
+# Contributor Covenant Code of Conduct
+
+## Our Pledge
+
+We as members, contributors, and leaders pledge to make participation in our
+community a harassment-free experience for everyone, regardless of age, body
+size, visible or invisible disability, ethnicity, sex characteristics, gender
+identity and expression, level of experience, education, socio-economic status,
+nationality, personal appearance, race, religion, or sexual identity
+and orientation.
+
+We pledge to act and interact in ways that contribute to an open, welcoming,
+diverse, inclusive, and healthy community.
+
+## Our Standards
+
+Examples of behavior that contributes to a positive environment for our
+community include:
+
+* Demonstrating empathy and kindness toward other people
+* Being respectful of differing opinions, viewpoints, and experiences
+* Giving and gracefully accepting constructive feedback
+* Accepting responsibility and apologizing to those affected by our mistakes,
+ and learning from the experience
+* Focusing on what is best not just for us as individuals, but for the
+ overall community
+
+Examples of unacceptable behavior include:
+
+* The use of sexualized language or imagery, and sexual attention or
+ advances of any kind
+* Trolling, insulting or derogatory comments, and personal or political attacks
+* Public or private harassment
+* Publishing others' private information, such as a physical or email
+ address, without their explicit permission
+* Other conduct which could reasonably be considered inappropriate in a
+ professional setting
+
+## Enforcement Responsibilities
+
+Community leaders are responsible for clarifying and enforcing our standards of
+acceptable behavior and will take appropriate and fair corrective action in
+response to any behavior that they deem inappropriate, threatening, offensive,
+or harmful.
+
+Community leaders have the right and responsibility to remove, edit, or reject
+comments, commits, code, wiki edits, issues, and other contributions that are
+not aligned to this Code of Conduct, and will communicate reasons for moderation
+decisions when appropriate.
+
+## Scope
+
+This Code of Conduct applies within all community spaces, and also applies when
+an individual is officially representing the community in public spaces.
+Examples of representing our community include using an official e-mail address,
+posting via an official social media account, or acting as an appointed
+representative at an online or offline event.
+
+## Enforcement
+
+Instances of abusive, harassing, or otherwise unacceptable behavior may be
+reported to the community leaders responsible for enforcement at
+edward@cheng.sydney.
+All complaints will be reviewed and investigated promptly and fairly.
+
+All community leaders are obligated to respect the privacy and security of the
+reporter of any incident.
+
+## Enforcement Guidelines
+
+Community leaders will follow these Community Impact Guidelines in determining
+the consequences for any action they deem in violation of this Code of Conduct:
+
+### 1. Correction
+
+**Community Impact**: Use of inappropriate language or other behavior deemed
+unprofessional or unwelcome in the community.
+
+**Consequence**: A private, written warning from community leaders, providing
+clarity around the nature of the violation and an explanation of why the
+behavior was inappropriate. A public apology may be requested.
+
+### 2. Warning
+
+**Community Impact**: A violation through a single incident or series
+of actions.
+
+**Consequence**: A warning with consequences for continued behavior. No
+interaction with the people involved, including unsolicited interaction with
+those enforcing the Code of Conduct, for a specified period of time. This
+includes avoiding interactions in community spaces as well as external channels
+like social media. Violating these terms may lead to a temporary or
+permanent ban.
+
+### 3. Temporary Ban
+
+**Community Impact**: A serious violation of community standards, including
+sustained inappropriate behavior.
+
+**Consequence**: A temporary ban from any sort of interaction or public
+communication with the community for a specified period of time. No public or
+private interaction with the people involved, including unsolicited interaction
+with those enforcing the Code of Conduct, is allowed during this period.
+Violating these terms may lead to a permanent ban.
+
+### 4. Permanent Ban
+
+**Community Impact**: Demonstrating a pattern of violation of community
+standards, including sustained inappropriate behavior, harassment of an
+individual, or aggression toward or disparagement of classes of individuals.
+
+**Consequence**: A permanent ban from any sort of public interaction within
+the community.
+
+## Attribution
+
+This Code of Conduct is adapted from the [Contributor Covenant][homepage],
+version 2.0, available at
+https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
+
+Community Impact Guidelines were inspired by [Mozilla's code of conduct
+enforcement ladder](https://github.com/mozilla/diversity).
+
+[homepage]: https://www.contributor-covenant.org
+
+For answers to common questions about this code of conduct, see the FAQ at
+https://www.contributor-covenant.org/faq. Translations are available at
+https://www.contributor-covenant.org/translations.
\ No newline at end of file
diff --git a/SECURITY.md b/SECURITY.md
new file mode 100644
index 0000000..028608a
--- /dev/null
+++ b/SECURITY.md
@@ -0,0 +1,28 @@
+# Security Policy
+
+## Supported Versions
+
+Use this section to tell people about which versions of your project are
+currently being supported with security updates.
+
+| Version | Supported |
+|---------|--------------------|
+| v1.x.x | :white_check_mark: |
+
+## Reporting a Vulnerability
+
+If you find a security vulnerability affecting any of our supported projects, please
+email [edward@cheng.sydney](mailto:edward@cheng.sydney), rather than opening a public issue on GitHub. After receiving
+the initial report, we will endeavor to keep you informed of the progress towards a fix and full announcement. We may
+ask you for additional information. You are also welcome to propose a patch or solution.
+
+Report security bugs in third-party modules to the person or team maintaining the module.
+
+## Coordinated Disclosure
+
+We aim to patch confirmed vulnerabilities within 30 days or less, disclosing the details of those vulnerabilities when a
+patch is published. We ask that you refrain from sharing your report with others while we work on our patch.
+
+We may want to coordinate an advisory with you to be published simultaneously with the patch, but you are also welcome
+to self-disclose after 90 days if you prefer. We will never publish information about you or our communications with you
+without your permission.
diff --git a/configuration/pom.xml b/configuration/pom.xml
new file mode 100644
index 0000000..07e8e2b
--- /dev/null
+++ b/configuration/pom.xml
@@ -0,0 +1,26 @@
+
+
+ 4.0.0
+
+ sydney.cheng
+ ec-microservice-commons
+ 1.0.0-SNAPSHOT
+
+
+ ec-microservice-commons-configuration
+
+
+ 17
+ 17
+ UTF-8
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-data-jpa
+
+
+
\ No newline at end of file
diff --git a/configuration/src/main/java/sydney/cheng/microservice/commons/configuration/properties/AppConfiguration.java b/configuration/src/main/java/sydney/cheng/microservice/commons/configuration/properties/AppConfiguration.java
new file mode 100644
index 0000000..4b5c390
--- /dev/null
+++ b/configuration/src/main/java/sydney/cheng/microservice/commons/configuration/properties/AppConfiguration.java
@@ -0,0 +1,26 @@
+package sydney.cheng.microservice.commons.configuration.properties;
+
+import lombok.AccessLevel;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.boot.context.properties.ConfigurationPropertiesScan;
+import org.springframework.context.annotation.Configuration;
+import sydney.cheng.microservice.commons.configuration.properties.auth.SecurityProperties;
+import sydney.cheng.microservice.commons.configuration.properties.database.DatabaseProperties;
+
+@Data
+@ConfigurationPropertiesScan(basePackages = {"sydney.cheng.**.properties"})
+@ConfigurationProperties("app")
+@Configuration
+public class AppConfiguration {
+ private ApplicationConfiguration application;
+ private SecurityProperties security;
+ private DatabaseProperties database;
+
+ @Data
+ @NoArgsConstructor(access = AccessLevel.PRIVATE)
+ public static class ApplicationConfiguration {
+ private String deploymentTarget;
+ }
+}
diff --git a/configuration/src/main/java/sydney/cheng/microservice/commons/configuration/properties/auth/CorsProperties.java b/configuration/src/main/java/sydney/cheng/microservice/commons/configuration/properties/auth/CorsProperties.java
new file mode 100644
index 0000000..f1baba5
--- /dev/null
+++ b/configuration/src/main/java/sydney/cheng/microservice/commons/configuration/properties/auth/CorsProperties.java
@@ -0,0 +1,32 @@
+package sydney.cheng.microservice.commons.configuration.properties.auth;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+import java.util.Arrays;
+import java.util.List;
+
+@Getter
+@Setter
+@NoArgsConstructor
+@AllArgsConstructor
+@ConfigurationProperties(prefix = "app.security.cors")
+public class CorsProperties {
+ private String allowedUrls;
+ private String allowedOrigins;
+ private List allowedHeaders;
+ private List allowedMethods;
+ private long allowedMaxAge = 3600;
+ private boolean allowCredentials = true;
+
+ public List getAllowedUrlList() {
+ return Arrays.asList(allowedUrls.split(","));
+ }
+
+ public List getAllowedOriginList() {
+ return Arrays.asList(allowedOrigins.split(","));
+ }
+}
diff --git a/configuration/src/main/java/sydney/cheng/microservice/commons/configuration/properties/auth/OAuth2Properties.java b/configuration/src/main/java/sydney/cheng/microservice/commons/configuration/properties/auth/OAuth2Properties.java
new file mode 100644
index 0000000..2a5b0b1
--- /dev/null
+++ b/configuration/src/main/java/sydney/cheng/microservice/commons/configuration/properties/auth/OAuth2Properties.java
@@ -0,0 +1,24 @@
+package sydney.cheng.microservice.commons.configuration.properties.auth;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+@Getter
+@Setter
+@NoArgsConstructor
+@AllArgsConstructor
+@ConfigurationProperties(prefix = "app.security.oauth2")
+public class OAuth2Properties {
+ private RemoteTokenCheckProperties remoteTokenCheck;
+
+ @Getter
+ @Setter
+ public static class RemoteTokenCheckProperties {
+ String checkTokenUrl;
+ String clientId;
+ String clientSecret;
+ }
+}
diff --git a/configuration/src/main/java/sydney/cheng/microservice/commons/configuration/properties/auth/SecurityProperties.java b/configuration/src/main/java/sydney/cheng/microservice/commons/configuration/properties/auth/SecurityProperties.java
new file mode 100644
index 0000000..00cda8f
--- /dev/null
+++ b/configuration/src/main/java/sydney/cheng/microservice/commons/configuration/properties/auth/SecurityProperties.java
@@ -0,0 +1,12 @@
+package sydney.cheng.microservice.commons.configuration.properties.auth;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+@Data
+@ConfigurationProperties("app.security")
+public class SecurityProperties {
+ private UrlProperties url;
+ private CorsProperties cors;
+ private OAuth2Properties oauth2;
+}
diff --git a/configuration/src/main/java/sydney/cheng/microservice/commons/configuration/properties/auth/UrlProperties.java b/configuration/src/main/java/sydney/cheng/microservice/commons/configuration/properties/auth/UrlProperties.java
new file mode 100644
index 0000000..436fa5c
--- /dev/null
+++ b/configuration/src/main/java/sydney/cheng/microservice/commons/configuration/properties/auth/UrlProperties.java
@@ -0,0 +1,13 @@
+package sydney.cheng.microservice.commons.configuration.properties.auth;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+@Data
+@ConfigurationProperties(prefix = "app.security.url")
+public class UrlProperties {
+ private String frontend;
+ private String backend;
+ private String gateway;
+ private String frontendDefaultLogin;
+}
diff --git a/configuration/src/main/java/sydney/cheng/microservice/commons/configuration/properties/database/DataSourceProperties.java b/configuration/src/main/java/sydney/cheng/microservice/commons/configuration/properties/database/DataSourceProperties.java
new file mode 100644
index 0000000..4ec281c
--- /dev/null
+++ b/configuration/src/main/java/sydney/cheng/microservice/commons/configuration/properties/database/DataSourceProperties.java
@@ -0,0 +1,18 @@
+package sydney.cheng.microservice.commons.configuration.properties.database;
+
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.springframework.context.annotation.Profile;
+
+import java.io.Serializable;
+
+@Profile("database")
+@EqualsAndHashCode(callSuper = true)
+@Data
+public class DataSourceProperties extends org.springframework.boot.autoconfigure.jdbc.DataSourceProperties implements Serializable {
+ private HikariDataSourceProperties hikari;
+
+ public DataSourceProperties() {
+ super();
+ }
+}
diff --git a/configuration/src/main/java/sydney/cheng/microservice/commons/configuration/properties/database/DatabaseNodeProperties.java b/configuration/src/main/java/sydney/cheng/microservice/commons/configuration/properties/database/DatabaseNodeProperties.java
new file mode 100644
index 0000000..58725d6
--- /dev/null
+++ b/configuration/src/main/java/sydney/cheng/microservice/commons/configuration/properties/database/DatabaseNodeProperties.java
@@ -0,0 +1,12 @@
+package sydney.cheng.microservice.commons.configuration.properties.database;
+
+import lombok.Data;
+import org.springframework.context.annotation.Profile;
+
+import java.io.Serializable;
+
+@Profile("database")
+@Data
+public class DatabaseNodeProperties implements Serializable {
+ DataSourceProperties datasource;
+}
diff --git a/configuration/src/main/java/sydney/cheng/microservice/commons/configuration/properties/database/DatabaseProperties.java b/configuration/src/main/java/sydney/cheng/microservice/commons/configuration/properties/database/DatabaseProperties.java
new file mode 100644
index 0000000..37a988c
--- /dev/null
+++ b/configuration/src/main/java/sydney/cheng/microservice/commons/configuration/properties/database/DatabaseProperties.java
@@ -0,0 +1,25 @@
+package sydney.cheng.microservice.commons.configuration.properties.database;
+
+import lombok.Data;
+import org.springframework.boot.autoconfigure.orm.jpa.JpaProperties;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Profile;
+import org.springframework.context.annotation.PropertySource;
+
+import java.io.Serializable;
+
+@Profile("database")
+@Data
+@PropertySource("classpath:application.yml")
+@ConfigurationProperties(prefix = "app.database")
+public class DatabaseProperties {
+ EntityManagerProperties entityManager;
+ JpaProperties jpa;
+ DatabaseNodeProperties master;
+ DatabaseNodeProperties replica;
+
+ @Data
+ public static class EntityManagerProperties implements Serializable {
+ String packages;
+ }
+}
diff --git a/configuration/src/main/java/sydney/cheng/microservice/commons/configuration/properties/database/HikariDataSourceProperties.java b/configuration/src/main/java/sydney/cheng/microservice/commons/configuration/properties/database/HikariDataSourceProperties.java
new file mode 100644
index 0000000..37a5e62
--- /dev/null
+++ b/configuration/src/main/java/sydney/cheng/microservice/commons/configuration/properties/database/HikariDataSourceProperties.java
@@ -0,0 +1,28 @@
+package sydney.cheng.microservice.commons.configuration.properties.database;
+
+import com.zaxxer.hikari.HikariConfig;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.springframework.context.annotation.Profile;
+
+import java.io.Serializable;
+import java.util.Properties;
+
+@Profile(value = {"database & hikari"})
+@EqualsAndHashCode(callSuper = true)
+@Data
+public class HikariDataSourceProperties extends HikariConfig implements Serializable {
+ private String urlPrefix;
+
+ public HikariDataSourceProperties() {
+ super();
+ }
+
+ public HikariDataSourceProperties(Properties properties) {
+ super(properties);
+ }
+
+ public HikariDataSourceProperties(String propertyFileName) {
+ super(propertyFileName);
+ }
+}
diff --git a/configuration/src/main/java/sydney/cheng/microservice/commons/configuration/properties/database/PrimaryHikariDataSourceProperties.java b/configuration/src/main/java/sydney/cheng/microservice/commons/configuration/properties/database/PrimaryHikariDataSourceProperties.java
new file mode 100644
index 0000000..0b922f5
--- /dev/null
+++ b/configuration/src/main/java/sydney/cheng/microservice/commons/configuration/properties/database/PrimaryHikariDataSourceProperties.java
@@ -0,0 +1,22 @@
+package sydney.cheng.microservice.commons.configuration.properties.database;
+
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Profile;
+
+import java.util.Properties;
+
+@Profile(value = {"database & hikari"})
+@ConfigurationProperties("app.database.primary.datasource.hikari")
+public class PrimaryHikariDataSourceProperties extends HikariDataSourceProperties {
+ public PrimaryHikariDataSourceProperties() {
+ super();
+ }
+
+ public PrimaryHikariDataSourceProperties(Properties properties) {
+ super(properties);
+ }
+
+ public PrimaryHikariDataSourceProperties(String propertyFileName) {
+ super(propertyFileName);
+ }
+}
diff --git a/configuration/src/main/java/sydney/cheng/microservice/commons/configuration/properties/database/ReplicaHikariDataSourceProperties.java b/configuration/src/main/java/sydney/cheng/microservice/commons/configuration/properties/database/ReplicaHikariDataSourceProperties.java
new file mode 100644
index 0000000..47435db
--- /dev/null
+++ b/configuration/src/main/java/sydney/cheng/microservice/commons/configuration/properties/database/ReplicaHikariDataSourceProperties.java
@@ -0,0 +1,22 @@
+package sydney.cheng.microservice.commons.configuration.properties.database;
+
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Profile;
+
+import java.util.Properties;
+
+@Profile(value = {"database & hikari"})
+@ConfigurationProperties("app.database.replica.datasource.hikari")
+public class ReplicaHikariDataSourceProperties extends HikariDataSourceProperties {
+ public ReplicaHikariDataSourceProperties() {
+ super();
+ }
+
+ public ReplicaHikariDataSourceProperties(String propertyFileName) {
+ super(propertyFileName);
+ }
+
+ public ReplicaHikariDataSourceProperties(Properties properties) {
+ super(properties);
+ }
+}
diff --git a/database/pom.xml b/database/pom.xml
new file mode 100644
index 0000000..7c10480
--- /dev/null
+++ b/database/pom.xml
@@ -0,0 +1,36 @@
+
+
+ 4.0.0
+
+ sydney.cheng
+ ec-microservice-commons
+ 1.0.0-SNAPSHOT
+
+
+ ec-microservice-commons-database
+
+
+ 17
+ 17
+ UTF-8
+
+
+
+
+ sydney.cheng
+ ec-microservice-commons-configuration
+ ${project.version}
+
+
+ org.springframework.boot
+ spring-boot-starter-cache
+
+
+ com.github.ben-manes.caffeine
+ caffeine
+ ${caffeine.version}
+
+
+
\ No newline at end of file
diff --git a/database/src/main/java/sydney/cheng/microservice/commons/database/annotation/DatabaseTransactional.java b/database/src/main/java/sydney/cheng/microservice/commons/database/annotation/DatabaseTransactional.java
new file mode 100644
index 0000000..9d3bdcc
--- /dev/null
+++ b/database/src/main/java/sydney/cheng/microservice/commons/database/annotation/DatabaseTransactional.java
@@ -0,0 +1,22 @@
+package sydney.cheng.microservice.commons.database.annotation;
+
+import org.springframework.context.annotation.Profile;
+import org.springframework.transaction.annotation.Propagation;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Profile("database")
+@Target({ElementType.METHOD, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@Transactional("ecTransactionManager")
+public @interface DatabaseTransactional {
+ boolean readOnly() default false;
+
+ Propagation propagation() default Propagation.REQUIRED;
+
+ Class extends Throwable>[] noRollbackFor() default {};
+}
diff --git a/database/src/main/java/sydney/cheng/microservice/commons/database/annotation/ReadOnlyConnection.java b/database/src/main/java/sydney/cheng/microservice/commons/database/annotation/ReadOnlyConnection.java
new file mode 100644
index 0000000..0fc5752
--- /dev/null
+++ b/database/src/main/java/sydney/cheng/microservice/commons/database/annotation/ReadOnlyConnection.java
@@ -0,0 +1,12 @@
+package sydney.cheng.microservice.commons.database.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Target({ElementType.METHOD, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface ReadOnlyConnection {
+
+}
\ No newline at end of file
diff --git a/database/src/main/java/sydney/cheng/microservice/commons/database/annotation/ReadOnlyConnectionInterceptor.java b/database/src/main/java/sydney/cheng/microservice/commons/database/annotation/ReadOnlyConnectionInterceptor.java
new file mode 100644
index 0000000..fb2ed59
--- /dev/null
+++ b/database/src/main/java/sydney/cheng/microservice/commons/database/annotation/ReadOnlyConnectionInterceptor.java
@@ -0,0 +1,52 @@
+package sydney.cheng.microservice.commons.database.annotation;
+
+import lombok.NoArgsConstructor;
+import lombok.ToString;
+import org.aspectj.lang.ProceedingJoinPoint;
+import org.aspectj.lang.annotation.Around;
+import org.aspectj.lang.annotation.Aspect;
+import org.aspectj.lang.annotation.Pointcut;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.core.Ordered;
+import org.springframework.stereotype.Component;
+import sydney.cheng.microservice.commons.database.constant.DbType;
+import sydney.cheng.microservice.commons.database.datasource.DbContextHolder;
+
+@Aspect
+@ToString
+@NoArgsConstructor
+@Component
+public class ReadOnlyConnectionInterceptor implements Ordered {
+ private int order;
+
+ @Override
+ public int getOrder() {
+ return order;
+ }
+
+ @Value("20")
+ public void setOrder(int order) {
+ this.order = order;
+ }
+
+ /*
+ * handle interceptor for any public method execution
+ */
+ @Pointcut(value = "execution(public * *(..))")
+ public void anyPublicMethod() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Around("@annotation(readOnlyConnection)")
+ public Object proceed(ProceedingJoinPoint pjp, ReadOnlyConnection readOnlyConnection) throws Throwable {
+ try {
+ DbContextHolder.setDbType(DbType.REPLICA);
+ Object result = pjp.proceed();
+ DbContextHolder.clearDbType();
+ return result;
+ } finally {
+ // restore state
+ DbContextHolder.clearDbType();
+ }
+ }
+}
diff --git a/database/src/main/java/sydney/cheng/microservice/commons/database/config/CacheManagerConfiguration.java b/database/src/main/java/sydney/cheng/microservice/commons/database/config/CacheManagerConfiguration.java
new file mode 100644
index 0000000..e601eb2
--- /dev/null
+++ b/database/src/main/java/sydney/cheng/microservice/commons/database/config/CacheManagerConfiguration.java
@@ -0,0 +1,26 @@
+package sydney.cheng.microservice.commons.database.config;
+
+import com.github.benmanes.caffeine.cache.Caffeine;
+import org.springframework.cache.CacheManager;
+import org.springframework.cache.annotation.EnableCaching;
+import org.springframework.cache.caffeine.CaffeineCacheManager;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import java.util.concurrent.TimeUnit;
+
+@EnableCaching
+@Configuration
+public class CacheManagerConfiguration {
+ @Bean
+ public Caffeine