diff --git a/.gitignore b/.gitignore
index 524f096..f480e8c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -22,3 +22,16 @@
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
replay_pid*
+api/target/
+api/src/main/java/META-INF/
+/api/src/main/resources/application-local.properties
+
+
+# IDE
+.idea
+*.iml
+api/src/main/resources/*.http
+api/*.iml
+.envrc
+shell.nix
+Session.vim
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..2291722
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,20 @@
+FROM artifacts.developer.gov.bc.ca/docker-remote/maven:3.8.1-openjdk-21-slim AS build
+WORKDIR /workspace/app
+
+COPY api/pom.xml .
+COPY api/src src
+RUN mvn package -DskipTests
+RUN mkdir -p target/dependency && (cd target/dependency; jar -xf ../*.jar)
+
+FROM artifacts.developer.gov.bc.ca/docker-remote/openjdk:21.0.0-jdk-oracle
+RUN useradd -ms /bin/bash spring
+RUN mkdir -p /logs
+RUN chown -R spring:spring /logs
+RUN chmod 755 /logs
+USER spring
+VOLUME /tmp
+ARG DEPENDENCY=/workspace/app/target/dependency
+COPY --from=build ${DEPENDENCY}/BOOT-INF/lib /app/lib
+COPY --from=build ${DEPENDENCY}/META-INF /app/META-INF
+COPY --from=build ${DEPENDENCY}/BOOT-INF/classes /app
+ENTRYPOINT ["java","-Duser.name=EAS-API","-Xms600m","-Xmx800m","-noverify","-XX:TieredStopAtLevel=1","-XX:+UseParallelGC","-XX:MinHeapFreeRatio=20","-XX:MaxHeapFreeRatio=40","-XX:GCTimeRatio=4","-XX:AdaptiveSizePolicyWeight=90","-XX:MaxMetaspaceSize=300m","-XX:ParallelGCThreads=2","-Djava.util.concurrent.ForkJoinPool.common.parallelism=8","-XX:CICompilerCount=2","-XX:+ExitOnOutOfMemoryError","-Dspring.profiles.active=openshift","-Djava.security.egd=file:/dev/./urandom","-cp","app:app/lib/*","ca.bc.gov.educ.eas.api.EasApiApplication"]
diff --git a/api/README.md b/api/README.md
new file mode 100644
index 0000000..135408b
--- /dev/null
+++ b/api/README.md
@@ -0,0 +1,14 @@
+# EDUC-EAS-API
+## Build Setup
+
+``` bash
+#Prepare to run
+- Connect to VPN
+- start up nats-server using `docker run -d --name=nats-main -p 4222:4222 -p 6222:6222 -p 8222:8222 nats -js`
+
+#Run application with local properties
+mvn clean install -Dspring.profiles.active=dev
+
+#Run application with default properties
+mvn clean install
+
diff --git a/api/pom.xml b/api/pom.xml
new file mode 100644
index 0000000..f8eedbc
--- /dev/null
+++ b/api/pom.xml
@@ -0,0 +1,406 @@
+
+
+ 4.0.0
+
+ ca.bc.gov.educ.eas
+ eas-api
+ 0.0.1-SNAPSHOT
+ eas-api
+ EAS API
+
+
+ java
+
+ src/test/**,
+ src/main/resources/**,
+ src/main/java/ca/bc/gov/educ/eas/api/endpoint/**,
+ src/main/java/ca/bc/gov/educ/eas/api/config/**,
+ src/main/java/ca/bc/gov/educ/eas/api/mappers/**,
+ src/main/java/ca/bc/gov/educ/eas/api/exception/**,
+ src/main/java/ca/bc/gov/educ/eas/api/model/**,
+ src/main/java/ca/bc/gov/educ/eas/api/struct/**
+
+ 18
+
+ 3.10.1
+ ${java.version}
+ ${java.version}
+ 1.5.3.Final
+ 4.20.0
+ 1.6.9
+ 2.11.0
+ 21.3.0.0
+ 33.2.1-jre
+ 4.0.4
+ 0.15
+ 4.0.3
+ 3.1.6
+ 2.17.1
+
+
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 3.0.13
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-security
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+ org.springframework.boot
+ spring-boot-starter-tomcat
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-validation
+
+
+ org.springframework.boot
+ spring-boot-starter-undertow
+
+
+ org.springframework.boot
+ spring-boot-starter-oauth2-client
+
+
+ org.springframework.boot
+ spring-boot-starter-webflux
+
+
+ org.springframework.boot
+ spring-boot-starter-oauth2-resource-server
+
+
+ org.springframework.boot
+ spring-boot-starter-data-jpa
+
+
+ org.flywaydb
+ flyway-core
+
+
+ com.oracle.database.jdbc
+ ojdbc11
+ ${ojdbc.version}
+
+
+ org.springframework.boot
+ spring-boot-starter-thymeleaf
+
+
+ org.apache.logging.log4j
+ log4j-api
+ ${log4j.version}
+
+
+ org.apache.logging.log4j
+ log4j-core
+ ${log4j.version}
+
+
+ org.apache.logging.log4j
+ log4j-to-slf4j
+ ${log4j.version}
+
+
+ com.github.albfernandez
+ juniversalchardet
+ 2.4.0
+
+
+ org.apache.commons
+ commons-lang3
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+ org.projectlombok
+ lombok
+ ${lombok.version}
+ true
+
+
+ org.springdoc
+ springdoc-openapi-starter-webmvc-ui
+ 2.0.2
+
+
+ org.mapstruct
+ mapstruct
+ ${org.mapstruct.version}
+
+
+ io.micrometer
+ micrometer-registry-prometheus
+
+
+ net.sf.jasperreports
+ jasperreports
+ 6.21.0
+
+
+ net.sf.jasperreports
+ jasperreports-fonts
+ 6.21.0
+
+
+ org.springframework.boot
+ spring-boot-starter-actuator
+
+
+
+ io.micrometer
+ micrometer-core
+
+
+ com.h2database
+ h2
+ test
+
+
+ com.fasterxml.jackson.datatype
+ jackson-datatype-jsr310
+ 2.14.1
+
+
+ org.springframework.security
+ spring-security-test
+ test
+
+
+ org.springframework
+ spring-context-indexer
+ true
+
+
+
+ net.javacrumbs.shedlock
+ shedlock-spring
+ ${shedlock.version}
+
+
+
+ net.javacrumbs.shedlock
+ shedlock-provider-jdbc-template
+ ${shedlock.version}
+
+
+ org.springframework.retry
+ spring-retry
+
+
+ net.sf.flatpack
+ flatpack
+ ${flatpack.version}
+
+
+ com.github.javafaker
+ javafaker
+ ${faker.varion}
+ test
+
+
+ org.awaitility
+ awaitility
+ ${awaitility.version}
+ test
+
+
+ org.awaitility
+ awaitility-proxy
+ ${awaitility-proxy.version}
+ test
+
+
+
+ pl.pragmatists
+ JUnitParams
+ 1.1.1
+ test
+
+
+ io.nats
+ jnats
+ ${nats.version}
+
+
+ com.google.guava
+ guava
+ ${guava.version}
+
+
+ commons-io
+ commons-io
+ 2.16.1
+
+
+ org.apache.commons
+ commons-csv
+ 1.11.0
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ ${maven.compiler.version}
+
+
+ org.sonarsource.scanner.maven
+ sonar-maven-plugin
+ 3.6.0.1398
+
+
+ org.jacoco
+ jacoco-maven-plugin
+ 0.8.8
+
+
+ org.hibernate.orm.tooling
+ hibernate-enhance-maven-plugin
+ ${hibernate.version}
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+
+ 1
+ true
+ false
+
+
+
+ org.apache.maven.plugins
+ maven-failsafe-plugin
+
+
+
+
+
+
+
+
+
+ central
+ https://repo1.maven.org/maven2
+
+
+ localrepository
+ file://${project.basedir}/src/main/resources/bcsans.jar
+
+
+
+
+ coverage
+
+ true
+
+
+
+
+ maven-compiler-plugin
+ ${maven.compiler.version}
+
+ ${java.version}
+ ${java.version}
+
+
+ org.mapstruct
+ mapstruct-processor
+ ${org.mapstruct.version}
+
+
+ org.projectlombok
+ lombok
+ ${lombok.version}
+
+
+ org.projectlombok
+ lombok-mapstruct-binding
+ 0.2.0
+
+
+ org.springframework
+ spring-context-indexer
+ ${spring-framework.version}
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-failsafe-plugin
+
+
+ integration-tests
+
+ integration-test
+ verify
+
+
+
+
+
+ org.hibernate.orm.tooling
+ hibernate-enhance-maven-plugin
+ ${hibernate.version}
+
+
+
+ true
+ true
+
+
+ enhance
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+ org.jacoco
+ jacoco-maven-plugin
+
+
+ prepare-agent
+
+ prepare-agent
+
+
+
+ coverage-report
+ prepare-package
+
+ report
+
+
+
+
+
+
+
+
+
diff --git a/api/src/main/java/ca/bc/gov/educ/eas/api/EasApiApplication.java b/api/src/main/java/ca/bc/gov/educ/eas/api/EasApiApplication.java
new file mode 100644
index 0000000..2f7b2df
--- /dev/null
+++ b/api/src/main/java/ca/bc/gov/educ/eas/api/EasApiApplication.java
@@ -0,0 +1,102 @@
+package ca.bc.gov.educ.eas.api;
+
+import lombok.val;
+import net.javacrumbs.shedlock.core.LockProvider;
+import net.javacrumbs.shedlock.provider.jdbctemplate.JdbcTemplateLockProvider;
+import net.javacrumbs.shedlock.spring.annotation.EnableSchedulerLock;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.cache.annotation.EnableCaching;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.retry.annotation.EnableRetry;
+import org.springframework.scheduling.annotation.EnableScheduling;
+import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
+import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
+import org.springframework.security.config.annotation.web.configurers.oauth2.server.resource.OAuth2ResourceServerConfigurer;
+import org.springframework.security.config.http.SessionCreationPolicy;
+import org.springframework.security.web.SecurityFilterChain;
+import org.springframework.transaction.PlatformTransactionManager;
+
+/**
+ * The EAS api application.
+ *
+ */
+@SpringBootApplication
+@EnableCaching
+@EnableScheduling
+@EnableSchedulerLock(defaultLockAtMostFor = "1s")
+@EnableRetry
+public class EasApiApplication {
+
+ /**
+ * The entry point of application.
+ *
+ * @param args the input arguments
+ */
+ public static void main(final String[] args) {
+ SpringApplication.run(EasApiApplication.class, args);
+ }
+
+ /**
+ * Lock provider For distributed lock, to avoid multiple pods executing the same scheduled task.
+ *
+ * @param jdbcTemplate the jdbc template
+ * @param transactionManager the transaction manager
+ * @return the lock provider
+ */
+ @Bean
+ public LockProvider lockProvider(@Autowired final JdbcTemplate jdbcTemplate,
+ @Autowired final PlatformTransactionManager transactionManager) {
+ return new JdbcTemplateLockProvider(jdbcTemplate, transactionManager,
+ "EAS_SHEDLOCK");
+ }
+
+ /**
+ * Thread pool task scheduler thread pool task scheduler.
+ *
+ * @return the thread pool task scheduler
+ */
+ @Bean
+ public ThreadPoolTaskScheduler threadPoolTaskScheduler() {
+ val threadPoolTaskScheduler = new ThreadPoolTaskScheduler();
+ threadPoolTaskScheduler.setPoolSize(5);
+ return threadPoolTaskScheduler;
+ }
+
+ /**
+ * The type Web security configuration. Add security exceptions for swagger UI and prometheus.
+ */
+ @Configuration
+ @EnableMethodSecurity
+ static
+ class WebSecurityConfiguration {
+
+ /**
+ * Instantiates a new Web security configuration. This makes sure that security context is
+ * propagated to async threads as well.
+ */
+ public WebSecurityConfiguration() {
+ super();
+ }
+
+ @Bean
+ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
+ http
+ .csrf(AbstractHttpConfigurer::disable)
+ .authorizeHttpRequests(auth -> auth
+ .requestMatchers("/v3/api-docs/**",
+ "/actuator/health", "/actuator/prometheus", "/actuator/**",
+ "/swagger-ui/**").permitAll()
+ .anyRequest().authenticated()
+ )
+ .sessionManagement(sess -> sess.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
+ .oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt);
+ return http.build();
+ }
+ }
+}
diff --git a/api/src/main/java/ca/bc/gov/educ/eas/api/adapter/CustomRequestBodyAdviceAdapter.java b/api/src/main/java/ca/bc/gov/educ/eas/api/adapter/CustomRequestBodyAdviceAdapter.java
new file mode 100644
index 0000000..bee0a07
--- /dev/null
+++ b/api/src/main/java/ca/bc/gov/educ/eas/api/adapter/CustomRequestBodyAdviceAdapter.java
@@ -0,0 +1,34 @@
+package ca.bc.gov.educ.eas.api.adapter;
+
+import jakarta.servlet.http.HttpServletRequest;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.core.MethodParameter;
+import org.springframework.http.HttpInputMessage;
+import org.springframework.http.converter.HttpMessageConverter;
+import org.springframework.web.bind.annotation.ControllerAdvice;
+import org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdviceAdapter;
+
+import java.lang.reflect.Type;
+
+@ControllerAdvice
+public class CustomRequestBodyAdviceAdapter extends RequestBodyAdviceAdapter {
+
+ HttpServletRequest httpServletRequest;
+
+ @Autowired
+ public void setHttpServletRequest(final HttpServletRequest httpServletRequest) {
+ this.httpServletRequest = httpServletRequest;
+ }
+
+ @Override
+ public boolean supports(final MethodParameter methodParameter, final Type type, final Class extends HttpMessageConverter>> aClass) {
+ return true;
+ }
+
+ @Override
+ public Object afterBodyRead(final Object body, final HttpInputMessage inputMessage, final MethodParameter parameter, final Type targetType,
+ final Class extends HttpMessageConverter>> converterType) {
+ this.httpServletRequest.setAttribute("payload", body);
+ return super.afterBodyRead(body, inputMessage, parameter, targetType, converterType);
+ }
+}
diff --git a/api/src/main/java/ca/bc/gov/educ/eas/api/config/AsyncConfiguration.java b/api/src/main/java/ca/bc/gov/educ/eas/api/config/AsyncConfiguration.java
new file mode 100644
index 0000000..1a380ce
--- /dev/null
+++ b/api/src/main/java/ca/bc/gov/educ/eas/api/config/AsyncConfiguration.java
@@ -0,0 +1,73 @@
+package ca.bc.gov.educ.eas.api.config;
+
+import ca.bc.gov.educ.eas.api.properties.ApplicationProperties;
+import ca.bc.gov.educ.eas.api.util.ThreadFactoryBuilder;
+import org.jboss.threads.EnhancedQueueExecutor;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Profile;
+import org.springframework.scheduling.annotation.EnableAsync;
+
+import java.time.Duration;
+import java.util.concurrent.Executor;
+
+@Configuration
+@EnableAsync
+@Profile("!test")
+public class AsyncConfiguration {
+ /**
+ * Thread pool task executor executor.
+ *
+ * @return the executor
+ */
+ @Bean(name = "subscriberExecutor")
+ @Autowired
+ public Executor threadPoolTaskExecutor(final ApplicationProperties applicationProperties) {
+ return new EnhancedQueueExecutor.Builder()
+ .setThreadFactory(new ThreadFactoryBuilder().withNameFormat("message-subscriber-%d").get())
+ .setCorePoolSize(applicationProperties.getMinSubscriberThreads()).setMaximumPoolSize(applicationProperties.getMaxSubscriberThreads()).setKeepAliveTime(Duration.ofSeconds(60)).build();
+ }
+
+ @Bean(name = "sagaRetryTaskExecutor")
+ public Executor sagaRetryTaskExecutor() {
+ return new EnhancedQueueExecutor.Builder()
+ .setThreadFactory(new ThreadFactoryBuilder().withNameFormat("async-saga-retry-executor-%d").get())
+ .setCorePoolSize(5).setMaximumPoolSize(10).setKeepAliveTime(Duration.ofSeconds(60)).build();
+ }
+
+ @Bean(name = "processLoadedStudentsTaskExecutor")
+ public Executor processLoadedStudentsTaskExecutor() {
+ return new EnhancedQueueExecutor.Builder()
+ .setThreadFactory(new ThreadFactoryBuilder().withNameFormat("async-loaded-stud-executor-%d").get())
+ .setCorePoolSize(5).setMaximumPoolSize(10).setKeepAliveTime(Duration.ofSeconds(60)).build();
+ }
+
+ @Bean(name = "processUncompletedSagasTaskExecutor")
+ public Executor processUncompletedSagasTaskExecutor() {
+ return new EnhancedQueueExecutor.Builder()
+ .setThreadFactory(new ThreadFactoryBuilder().withNameFormat("async-uncompleted-saga-executor-%d").get())
+ .setCorePoolSize(5).setMaximumPoolSize(10).setKeepAliveTime(Duration.ofSeconds(60)).build();
+ }
+
+ @Bean(name = "findSchoolCollectionsForSubmissionTaskExecutor")
+ public Executor findSchoolCollectionsForSubmissionTaskExecutor() {
+ return new EnhancedQueueExecutor.Builder()
+ .setThreadFactory(new ThreadFactoryBuilder().withNameFormat("async-school-collections-executor-%d").get())
+ .setCorePoolSize(5).setMaximumPoolSize(10).setKeepAliveTime(Duration.ofSeconds(60)).build();
+ }
+
+ @Bean(name = "findAllUnsubmittedIndependentSchoolsTaskExecutor")
+ public Executor findAllUnsubmittedIndependentSchoolsTaskExecutor() {
+ return new EnhancedQueueExecutor.Builder()
+ .setThreadFactory(new ThreadFactoryBuilder().withNameFormat("async-unsubmitted-indies-executor-%d").get())
+ .setCorePoolSize(5).setMaximumPoolSize(10).setKeepAliveTime(Duration.ofSeconds(60)).build();
+ }
+
+ @Bean(name = "publisherExecutor")
+ public Executor publisherExecutor() {
+ return new EnhancedQueueExecutor.Builder()
+ .setThreadFactory(new com.google.common.util.concurrent.ThreadFactoryBuilder().setNameFormat("message-publisher-%d").build())
+ .setCorePoolSize(5).setMaximumPoolSize(10).setKeepAliveTime(Duration.ofSeconds(60)).build();
+ }
+}
diff --git a/api/src/main/java/ca/bc/gov/educ/eas/api/config/EasAPIMVCConfig.java b/api/src/main/java/ca/bc/gov/educ/eas/api/config/EasAPIMVCConfig.java
new file mode 100644
index 0000000..d1e0a89
--- /dev/null
+++ b/api/src/main/java/ca/bc/gov/educ/eas/api/config/EasAPIMVCConfig.java
@@ -0,0 +1,43 @@
+package ca.bc.gov.educ.eas.api.config;
+
+import lombok.AccessLevel;
+import lombok.Getter;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+
+/**
+ * The type Pen reg api mvc config.
+ *
+ * @author Om
+ */
+@Configuration
+public class EasAPIMVCConfig implements WebMvcConfigurer {
+
+ /**
+ * The Pen reg api interceptor.
+ */
+ @Getter(AccessLevel.PRIVATE)
+ private final RequestResponseInterceptor requestResponseInterceptor;
+
+ /**
+ * Instantiates a new Pen reg api mvc config.
+ *
+ * @param requestResponseInterceptor the pen reg api interceptor
+ */
+ @Autowired
+ public EasAPIMVCConfig(final RequestResponseInterceptor requestResponseInterceptor) {
+ this.requestResponseInterceptor = requestResponseInterceptor;
+ }
+
+ /**
+ * Add interceptors.
+ *
+ * @param registry the registry
+ */
+ @Override
+ public void addInterceptors(InterceptorRegistry registry) {
+ registry.addInterceptor(requestResponseInterceptor).addPathPatterns("/**");
+ }
+}
diff --git a/api/src/main/java/ca/bc/gov/educ/eas/api/config/RequestResponseInterceptor.java b/api/src/main/java/ca/bc/gov/educ/eas/api/config/RequestResponseInterceptor.java
new file mode 100644
index 0000000..a77d6a6
--- /dev/null
+++ b/api/src/main/java/ca/bc/gov/educ/eas/api/config/RequestResponseInterceptor.java
@@ -0,0 +1,47 @@
+package ca.bc.gov.educ.eas.api.config;
+
+import ca.bc.gov.educ.eas.api.helpers.LogHelper;
+import ca.bc.gov.educ.eas.api.properties.ApplicationProperties;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import lombok.extern.slf4j.Slf4j;
+import lombok.val;
+import org.springframework.lang.NonNull;
+import org.springframework.stereotype.Component;
+import org.springframework.web.servlet.AsyncHandlerInterceptor;
+
+import java.time.Instant;
+
+@Component
+@Slf4j
+public class RequestResponseInterceptor implements AsyncHandlerInterceptor {
+
+ @Override
+ public boolean preHandle(final HttpServletRequest request, final HttpServletResponse response, final Object handler) {
+ // for async this is called twice so need a check to avoid setting twice.
+ if (request.getAttribute("startTime") == null) {
+ final long startTime = Instant.now().toEpochMilli();
+ request.setAttribute("startTime", startTime);
+ }
+ return true;
+ }
+
+ /**
+ * After completion.
+ *
+ * @param request the request
+ * @param response the response
+ * @param handler the handler
+ * @param ex the ex
+ */
+ @Override
+ public void afterCompletion(@NonNull final HttpServletRequest request, final HttpServletResponse response, @NonNull final Object handler, final Exception ex) {
+ LogHelper.logServerHttpReqResponseDetails(request, response);
+ val correlationID = request.getHeader(ApplicationProperties.CORRELATION_ID);
+ if (correlationID != null) {
+ response.setHeader(ApplicationProperties.CORRELATION_ID, request.getHeader(ApplicationProperties.CORRELATION_ID));
+ }
+ }
+
+
+}
diff --git a/api/src/main/java/ca/bc/gov/educ/eas/api/config/TemplateEngineConfig.java b/api/src/main/java/ca/bc/gov/educ/eas/api/config/TemplateEngineConfig.java
new file mode 100644
index 0000000..7ae30dc
--- /dev/null
+++ b/api/src/main/java/ca/bc/gov/educ/eas/api/config/TemplateEngineConfig.java
@@ -0,0 +1,34 @@
+package ca.bc.gov.educ.eas.api.config;
+
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.thymeleaf.spring6.SpringTemplateEngine;
+import org.thymeleaf.templatemode.TemplateMode;
+import org.thymeleaf.templateresolver.ITemplateResolver;
+import org.thymeleaf.templateresolver.StringTemplateResolver;
+
+import java.util.HashMap;
+import java.util.Map;
+
+@Configuration
+public class TemplateEngineConfig {
+ @Bean
+ public SpringTemplateEngine emailTemplateEngine() {
+ final SpringTemplateEngine templateEngine = new SpringTemplateEngine();
+ templateEngine.setTemplateResolver(htmlTemplateResolver());
+ return templateEngine;
+ }
+
+ private ITemplateResolver htmlTemplateResolver() {
+ final var templateResolver = new StringTemplateResolver();
+ templateResolver.setTemplateMode(TemplateMode.HTML);
+ return templateResolver;
+ }
+
+ @Bean
+ @ConfigurationProperties(prefix = "email.template")
+ public Map templateConfig() {
+ return new HashMap<>();
+ }
+}
diff --git a/api/src/main/java/ca/bc/gov/educ/eas/api/constants/EventOutcome.java b/api/src/main/java/ca/bc/gov/educ/eas/api/constants/EventOutcome.java
new file mode 100644
index 0000000..73a1349
--- /dev/null
+++ b/api/src/main/java/ca/bc/gov/educ/eas/api/constants/EventOutcome.java
@@ -0,0 +1,9 @@
+package ca.bc.gov.educ.eas.api.constants;
+
+/**
+ * The enum Event outcome.
+ */
+public enum EventOutcome {
+ INITIATE_SUCCESS,
+ SAGA_COMPLETED,
+}
diff --git a/api/src/main/java/ca/bc/gov/educ/eas/api/constants/EventStatus.java b/api/src/main/java/ca/bc/gov/educ/eas/api/constants/EventStatus.java
new file mode 100644
index 0000000..406e074
--- /dev/null
+++ b/api/src/main/java/ca/bc/gov/educ/eas/api/constants/EventStatus.java
@@ -0,0 +1,15 @@
+package ca.bc.gov.educ.eas.api.constants;
+
+/**
+ * The enum Event status.
+ */
+public enum EventStatus {
+ /**
+ * Db committed event status.
+ */
+ DB_COMMITTED,
+ /**
+ * Message published event status.
+ */
+ MESSAGE_PUBLISHED
+}
diff --git a/api/src/main/java/ca/bc/gov/educ/eas/api/constants/EventType.java b/api/src/main/java/ca/bc/gov/educ/eas/api/constants/EventType.java
new file mode 100644
index 0000000..d170674
--- /dev/null
+++ b/api/src/main/java/ca/bc/gov/educ/eas/api/constants/EventType.java
@@ -0,0 +1,11 @@
+package ca.bc.gov.educ.eas.api.constants;
+
+/**
+ * The enum Event type.
+ */
+public enum EventType {
+ READ_FROM_TOPIC,
+ INITIATED,
+ MARK_SAGA_COMPLETE,
+ GET_PAGINATED_SCHOOLS
+}
diff --git a/api/src/main/java/ca/bc/gov/educ/eas/api/constants/SagaEnum.java b/api/src/main/java/ca/bc/gov/educ/eas/api/constants/SagaEnum.java
new file mode 100644
index 0000000..9779801
--- /dev/null
+++ b/api/src/main/java/ca/bc/gov/educ/eas/api/constants/SagaEnum.java
@@ -0,0 +1,5 @@
+package ca.bc.gov.educ.eas.api.constants;
+
+public enum SagaEnum {
+
+}
diff --git a/api/src/main/java/ca/bc/gov/educ/eas/api/constants/SagaLogMessages.java b/api/src/main/java/ca/bc/gov/educ/eas/api/constants/SagaLogMessages.java
new file mode 100644
index 0000000..928378d
--- /dev/null
+++ b/api/src/main/java/ca/bc/gov/educ/eas/api/constants/SagaLogMessages.java
@@ -0,0 +1,25 @@
+package ca.bc.gov.educ.eas.api.constants;
+
+import lombok.Getter;
+
+public enum SagaLogMessages {
+ NO_RECORD_SAGA_ID_EVENT_TYPE("no record found for the saga id and event type combination, processing."),
+ RECORD_FOUND_FOR_SAGA_ID_EVENT_TYPE("record found for the saga id and event type combination, might be a duplicate or replay," +
+ " just updating the db status so that it will be polled and sent back again."),
+ EVENT_PAYLOAD("event is :: {}");
+
+ /**
+ * The Message.
+ */
+ @Getter
+ private final String message;
+
+ /**
+ * Instantiates a new sage log message.
+ *
+ * @param message the message
+ */
+ SagaLogMessages(String message) {
+ this.message = message;
+ }
+}
diff --git a/api/src/main/java/ca/bc/gov/educ/eas/api/constants/SagaStatusEnum.java b/api/src/main/java/ca/bc/gov/educ/eas/api/constants/SagaStatusEnum.java
new file mode 100644
index 0000000..1a8c9dd
--- /dev/null
+++ b/api/src/main/java/ca/bc/gov/educ/eas/api/constants/SagaStatusEnum.java
@@ -0,0 +1,23 @@
+package ca.bc.gov.educ.eas.api.constants;
+
+/**
+ * The enum Saga status enum.
+ */
+public enum SagaStatusEnum{
+ /**
+ * Started saga status enum.
+ */
+ STARTED,
+ /**
+ * In progress saga status enum.
+ */
+ IN_PROGRESS,
+ /**
+ * Completed saga status enum.
+ */
+ COMPLETED,
+ /**
+ * Force stopped saga status enum.
+ */
+ FORCE_STOPPED
+}
diff --git a/api/src/main/java/ca/bc/gov/educ/eas/api/constants/TopicsEnum.java b/api/src/main/java/ca/bc/gov/educ/eas/api/constants/TopicsEnum.java
new file mode 100644
index 0000000..4d44455
--- /dev/null
+++ b/api/src/main/java/ca/bc/gov/educ/eas/api/constants/TopicsEnum.java
@@ -0,0 +1,8 @@
+package ca.bc.gov.educ.eas.api.constants;
+
+
+public enum TopicsEnum {
+ EAS_API_TOPIC,
+ INSTITUTE_API_TOPIC,
+ EAS_EVENTS_TOPIC,
+}
diff --git a/api/src/main/java/ca/bc/gov/educ/eas/api/constants/v1/FacilityTypeCodes.java b/api/src/main/java/ca/bc/gov/educ/eas/api/constants/v1/FacilityTypeCodes.java
new file mode 100644
index 0000000..d551a4c
--- /dev/null
+++ b/api/src/main/java/ca/bc/gov/educ/eas/api/constants/v1/FacilityTypeCodes.java
@@ -0,0 +1,43 @@
+package ca.bc.gov.educ.eas.api.constants.v1;
+
+import lombok.Getter;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * The enum for school's facility type codes
+ */
+@Getter
+public enum FacilityTypeCodes {
+ PROVINCIAL("PROVINCIAL"),
+ DIST_CONT("DIST_CONT"),
+ ELEC_DELIV("ELEC_DELIV"),
+ STANDARD("STANDARD"),
+ CONT_ED("CONT_ED"),
+ DIST_LEARN("DIST_LEARN"),
+ ALT_PROGS("ALT_PROGS"),
+ STRONG_CEN("STRONG_CEN"),
+ STRONG_OUT("STRONG_OUT"),
+ SHORT_PRP("SHORT_PRP"),
+ LONG_PRP("LONG_PRP"),
+ SUMMER("SUMMER"),
+ YOUTH("YOUTH"),
+ DISTONLINE("DISTONLINE"),
+ POST_SEC("POST_SEC"),
+ JUSTB4PRO("JUSTB4PRO");
+
+ private final String code;
+ FacilityTypeCodes(String code) { this.code = code; }
+
+ public static String[] getFacilityCodesWithoutOLAndCE(){
+ return new String[]{ALT_PROGS.getCode(), JUSTB4PRO.getCode(), LONG_PRP.getCode(), POST_SEC.getCode(), SHORT_PRP.getCode(), STANDARD.getCode(), STRONG_CEN.getCode(), STRONG_OUT.getCode(), SUMMER.getCode(), YOUTH.getCode()};
+ }
+
+ public static List getOnlineFacilityTypeCodes() {
+ List codes = new ArrayList<>();
+ codes.add(DIST_LEARN.getCode());
+ codes.add(DISTONLINE.getCode());
+ return codes;
+ }
+}
diff --git a/api/src/main/java/ca/bc/gov/educ/eas/api/constants/v1/SchoolCategoryCodes.java b/api/src/main/java/ca/bc/gov/educ/eas/api/constants/v1/SchoolCategoryCodes.java
new file mode 100644
index 0000000..06d9e30
--- /dev/null
+++ b/api/src/main/java/ca/bc/gov/educ/eas/api/constants/v1/SchoolCategoryCodes.java
@@ -0,0 +1,34 @@
+package ca.bc.gov.educ.eas.api.constants.v1;
+
+import lombok.Getter;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * The enum for school category codes
+ */
+@Getter
+public enum SchoolCategoryCodes {
+ IMM_DATA("IMM_DATA"),
+ CHILD_CARE("CHILD_CARE"),
+ MISC("MISC"),
+ PUBLIC("PUBLIC"),
+ INDEPEND("INDEPEND"),
+ FED_BAND("FED_BAND"),
+ OFFSHORE("OFFSHORE"),
+ EAR_LEARN("EAR_LEARN"),
+ YUKON("YUKON"),
+ POST_SEC("POST_SEC"),
+ INDP_FNS("INDP_FNS");
+
+ private final String code;
+ public static final Set INDEPENDENTS = new HashSet<>(Arrays.asList(INDEPEND.getCode(), INDP_FNS.getCode()));
+ public static final Set INDEPENDENTS_AND_OFFSHORE = new HashSet<>(Arrays.asList(INDEPEND.getCode(), INDP_FNS.getCode(), OFFSHORE.getCode()));
+ SchoolCategoryCodes(String code) { this.code = code; }
+
+ public static String[] getActiveSchoolCategoryCodes(){
+ return new String[]{EAR_LEARN.getCode(), FED_BAND.getCode(), INDEPEND.getCode(), INDP_FNS.getCode(), OFFSHORE.getCode(), POST_SEC.getCode(), PUBLIC.getCode(), YUKON.getCode()};
+ }
+}
diff --git a/api/src/main/java/ca/bc/gov/educ/eas/api/constants/v1/SchoolFundingCodes.java b/api/src/main/java/ca/bc/gov/educ/eas/api/constants/v1/SchoolFundingCodes.java
new file mode 100644
index 0000000..d73b053
--- /dev/null
+++ b/api/src/main/java/ca/bc/gov/educ/eas/api/constants/v1/SchoolFundingCodes.java
@@ -0,0 +1,29 @@
+package ca.bc.gov.educ.eas.api.constants.v1;
+
+import lombok.Getter;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Optional;
+import java.util.stream.Collectors;
+
+public enum SchoolFundingCodes {
+ EDUC_SERVICE_CHILDREN("05"),
+ OUT_OF_PROVINCE("14"),
+ NEWCOMER_REFUGEE("16"),
+ STATUS_FIRST_NATION("20");
+
+ @Getter
+ private final String code;
+ SchoolFundingCodes(String code) {
+ this.code = code;
+ }
+
+ public static Optional findByValue(String value) {
+ return Arrays.stream(values()).filter(e -> Arrays.asList(e.code).contains(value)).findFirst();
+ }
+
+ public static List getCodes() {
+ return Arrays.stream(SchoolFundingCodes.values()).map(SchoolFundingCodes::getCode).collect(Collectors.toList());
+ }
+}
diff --git a/api/src/main/java/ca/bc/gov/educ/eas/api/constants/v1/SchoolGradeCodes.java b/api/src/main/java/ca/bc/gov/educ/eas/api/constants/v1/SchoolGradeCodes.java
new file mode 100644
index 0000000..60e4986
--- /dev/null
+++ b/api/src/main/java/ca/bc/gov/educ/eas/api/constants/v1/SchoolGradeCodes.java
@@ -0,0 +1,311 @@
+package ca.bc.gov.educ.eas.api.constants.v1;
+
+import lombok.Getter;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Optional;
+
+@Getter
+public enum SchoolGradeCodes {
+ KINDHALF("KINDHALF", "KH", 1),
+ KINDFULL("KINDFULL","KF", 2),
+ GRADE01("GRADE01","01", 3),
+ GRADE02("GRADE02","02", 4),
+ GRADE03("GRADE03","03", 5),
+ GRADE04("GRADE04","04", 6),
+ GRADE05("GRADE05","05", 7),
+ GRADE06("GRADE06","06", 8),
+ GRADE07("GRADE07","07", 9),
+ ELEMUNGR("ELEMUNGR","EU", 10),
+ GRADE08("GRADE08","08", 11),
+ GRADE09("GRADE09","09", 12),
+ GRADE10("GRADE10","10", 13),
+ GRADE11("GRADE11","11", 14),
+ GRADE12("GRADE12","12", 15),
+ SECONDARY_UNGRADED("SECUNGR","SU", 16),
+ GRADUATED_ADULT("GRADULT","GA", 17),
+ HOMESCHOOL("HOMESCL","HS", 18);
+
+ @Getter
+ private final String code;
+ @Getter
+ private final String typeCode;
+ @Getter
+ private final int sequence;
+ SchoolGradeCodes(String typeCode, String code, int sequence) {
+ this.code = code;
+ this.typeCode = typeCode;
+ this.sequence = sequence;
+ }
+
+ public static Optional findByValue(String value) {
+ return Arrays.stream(values()).filter(e -> Arrays.asList(e.code).contains(value)).findFirst();
+ }
+
+ public static Optional findByTypeCode(String typeCode) {
+ return Arrays.stream(values()).filter(e -> Arrays.asList(e.typeCode).contains(typeCode)).findFirst();
+ }
+
+ /**
+ * Get all grades HS, K-9, and EU
+ * @return - all grades HS, K-9, and EU
+ */
+ public static List getDistrictFundingGrades() {
+ List codes = new ArrayList<>();
+ codes.add(HOMESCHOOL.getCode());
+ codes.addAll(getKToNineGrades());
+ return codes;
+ }
+
+ public static List getKfOneToSevenEuGrades() {
+ List codes = new ArrayList<>();
+ codes.add(KINDFULL.getCode());
+ codes.add(GRADE01.getCode());
+ codes.add(GRADE02.getCode());
+ codes.add(GRADE03.getCode());
+ codes.add(GRADE04.getCode());
+ codes.add(GRADE05.getCode());
+ codes.add(GRADE06.getCode());
+ codes.add(GRADE07.getCode());
+ codes.add(ELEMUNGR.getCode());
+ return codes;
+ }
+
+ public static List getKToSevenEuGrades() {
+ List codes = new ArrayList<>();
+ codes.add(KINDHALF.getCode());
+ codes.add(KINDFULL.getCode());
+ codes.add(GRADE01.getCode());
+ codes.add(GRADE02.getCode());
+ codes.add(GRADE03.getCode());
+ codes.add(GRADE04.getCode());
+ codes.add(GRADE05.getCode());
+ codes.add(GRADE06.getCode());
+ codes.add(GRADE07.getCode());
+ codes.add(ELEMUNGR.getCode());
+ return codes;
+ }
+
+ public static List getKToNineGrades() {
+ List codes = new ArrayList<>();
+ codes.add(KINDHALF.getCode());
+ codes.add(KINDFULL.getCode());
+ codes.add(GRADE01.getCode());
+ codes.add(GRADE02.getCode());
+ codes.add(GRADE03.getCode());
+ codes.add(GRADE04.getCode());
+ codes.add(GRADE05.getCode());
+ codes.add(GRADE06.getCode());
+ codes.add(GRADE07.getCode());
+ codes.add(ELEMUNGR.getCode());
+ codes.add(GRADE08.getCode());
+ codes.add(GRADE09.getCode());
+ return codes;
+ }
+ public static List get1To7Grades() {
+ List codes = new ArrayList<>();
+ codes.add(GRADE01.getCode());
+ codes.add(GRADE02.getCode());
+ codes.add(GRADE03.getCode());
+ codes.add(GRADE04.getCode());
+ codes.add(GRADE05.getCode());
+ codes.add(GRADE06.getCode());
+ codes.add(GRADE07.getCode());
+ return codes;
+ }
+
+ public static List get8To12Grades() {
+ List codes = new ArrayList<>();
+ codes.add(GRADE08.getCode());
+ codes.add(GRADE09.getCode());
+ codes.add(GRADE10.getCode());
+ codes.add(GRADE11.getCode());
+ codes.add(GRADE12.getCode());
+ return codes;
+ }
+
+ public static List get8PlusGrades() {
+ List codes = new ArrayList<>();
+ codes.add(GRADE08.getCode());
+ codes.add(GRADE09.getCode());
+ codes.add(GRADE10.getCode());
+ codes.add(GRADE11.getCode());
+ codes.add(GRADE12.getCode());
+ codes.add(SECONDARY_UNGRADED.getCode());
+ codes.add(GRADUATED_ADULT.getCode());
+ return codes;
+ }
+
+ public static List get8PlusGradesNoGA() {
+ List codes = new ArrayList<>();
+ codes.add(GRADE08.getCode());
+ codes.add(GRADE09.getCode());
+ codes.add(GRADE10.getCode());
+ codes.add(GRADE11.getCode());
+ codes.add(GRADE12.getCode());
+ codes.add(SECONDARY_UNGRADED.getCode());
+ return codes;
+ }
+
+ public static List getHighSchoolGrades() {
+ return getGrades10toSU();
+ }
+
+ public static List getAllowedAdultGrades() {
+ List codes = new ArrayList<>();
+ codes.add(GRADE10.getCode());
+ codes.add(GRADE11.getCode());
+ codes.add(GRADE12.getCode());
+ codes.add(SECONDARY_UNGRADED.getCode());
+ codes.add(GRADUATED_ADULT.getCode());
+ return codes;
+ }
+
+ public static List getAllowedAdultGradesNonGraduate() {
+ return getGrades10toSU();
+ }
+
+ public static List getSummerSchoolGrades() {
+ List codes = new ArrayList<>();
+ codes.add(GRADE01.getCode());
+ codes.add(GRADE02.getCode());
+ codes.add(GRADE03.getCode());
+ codes.add(GRADE04.getCode());
+ codes.add(GRADE05.getCode());
+ codes.add(GRADE06.getCode());
+ codes.add(GRADE07.getCode());
+ codes.add(GRADE08.getCode());
+ codes.add(GRADE09.getCode());
+ codes.add(GRADE10.getCode());
+ codes.add(GRADE11.getCode());
+ codes.add(GRADE12.getCode());
+ return codes;
+ }
+
+ public static List getSupportBlockGrades() {
+ return getGrades10toSU();
+ }
+
+ public static List getGrades10toSU() {
+ List codes = new ArrayList<>();
+ codes.add(GRADE10.getCode());
+ codes.add(GRADE11.getCode());
+ codes.add(GRADE12.getCode());
+ codes.add(SECONDARY_UNGRADED.getCode());
+ return codes;
+ }
+
+ public static List getGrades8and9() {
+ List codes = new ArrayList<>();
+ codes.add(GRADE08.getCode());
+ codes.add(GRADE09.getCode());
+ return codes;
+ }
+
+ public static List getAllSchoolGrades() {
+ List codes = new ArrayList<>();
+ codes.add(KINDHALF.getCode());
+ codes.add(KINDFULL.getCode());
+ codes.add(GRADE01.getCode());
+ codes.add(GRADE02.getCode());
+ codes.add(GRADE03.getCode());
+ codes.add(GRADE04.getCode());
+ codes.add(GRADE05.getCode());
+ codes.add(GRADE06.getCode());
+ codes.add(GRADE07.getCode());
+ codes.add(ELEMUNGR.getCode());
+ codes.add(GRADE08.getCode());
+ codes.add(GRADE09.getCode());
+ codes.add(GRADE10.getCode());
+ codes.add(GRADE11.getCode());
+ codes.add(GRADE12.getCode());
+ codes.add(SECONDARY_UNGRADED.getCode());
+ codes.add(GRADUATED_ADULT.getCode());
+ codes.add(HOMESCHOOL.getCode());
+ return codes;
+ }
+
+ public static List getAllSchoolGradesExcludingHS(){
+ List codes = new ArrayList<>();
+ codes.add(KINDHALF.getCode());
+ codes.add(KINDFULL.getCode());
+ codes.add(GRADE01.getCode());
+ codes.add(GRADE02.getCode());
+ codes.add(GRADE03.getCode());
+ codes.add(GRADE04.getCode());
+ codes.add(GRADE05.getCode());
+ codes.add(GRADE06.getCode());
+ codes.add(GRADE07.getCode());
+ codes.add(ELEMUNGR.getCode());
+ codes.add(GRADE08.getCode());
+ codes.add(GRADE09.getCode());
+ codes.add(GRADE10.getCode());
+ codes.add(GRADE11.getCode());
+ codes.add(GRADE12.getCode());
+ codes.add(SECONDARY_UNGRADED.getCode());
+ codes.add(GRADUATED_ADULT.getCode());
+ return codes;
+ }
+
+ public static List getNonIndependentSchoolGrades() {
+ List codes = new ArrayList<>();
+ codes.add(KINDFULL.getCode());
+ codes.add(GRADE01.getCode());
+ codes.add(GRADE02.getCode());
+ codes.add(GRADE03.getCode());
+ codes.add(GRADE04.getCode());
+ codes.add(GRADE05.getCode());
+ codes.add(GRADE06.getCode());
+ codes.add(GRADE07.getCode());
+ codes.add(ELEMUNGR.getCode());
+ codes.add(GRADE08.getCode());
+ codes.add(GRADE09.getCode());
+ codes.add(GRADE10.getCode());
+ codes.add(GRADE11.getCode());
+ codes.add(GRADE12.getCode());
+ codes.add(SECONDARY_UNGRADED.getCode());
+ codes.add(GRADUATED_ADULT.getCode());
+ codes.add(HOMESCHOOL.getCode());
+ return codes;
+ }
+ public static List getNonIndependentKtoSUGrades() {
+ List codes = new ArrayList<>();
+ codes.add(KINDFULL.getCode());
+ codes.add(GRADE01.getCode());
+ codes.add(GRADE02.getCode());
+ codes.add(GRADE03.getCode());
+ codes.add(GRADE04.getCode());
+ codes.add(GRADE05.getCode());
+ codes.add(GRADE06.getCode());
+ codes.add(GRADE07.getCode());
+ codes.add(ELEMUNGR.getCode());
+ codes.add(GRADE08.getCode());
+ codes.add(GRADE09.getCode());
+ codes.add(GRADE10.getCode());
+ codes.add(GRADE11.getCode());
+ codes.add(GRADE12.getCode());
+ codes.add(SECONDARY_UNGRADED.getCode());
+ return codes;
+ }
+
+ public static List getIndependentKtoSUGrades() {
+ List codes = getKToNineGrades();
+ codes.addAll(getGrades10toSU());
+ return codes;
+ }
+
+ public static List getNonIndependentKtoGAGrades() {
+ List codes = new ArrayList<>(getNonIndependentKtoSUGrades());
+ codes.add(GRADUATED_ADULT.getCode());
+ return codes;
+ }
+
+ public static List getIndependentKtoGAGrades() {
+ List codes = new ArrayList<>();
+ codes.add(KINDHALF.getCode());
+ codes.addAll(getNonIndependentKtoGAGrades());
+ return codes;
+ }
+}
diff --git a/api/src/main/java/ca/bc/gov/educ/eas/api/constants/v1/URL.java b/api/src/main/java/ca/bc/gov/educ/eas/api/constants/v1/URL.java
new file mode 100644
index 0000000..3e2eacc
--- /dev/null
+++ b/api/src/main/java/ca/bc/gov/educ/eas/api/constants/v1/URL.java
@@ -0,0 +1,10 @@
+package ca.bc.gov.educ.eas.api.constants.v1;
+
+public final class URL {
+
+ private URL(){
+ }
+
+ public static final String BASE_URL="/api/v1/eas";
+
+}
diff --git a/api/src/main/java/ca/bc/gov/educ/eas/api/controller/v1/HelloWorldController.java b/api/src/main/java/ca/bc/gov/educ/eas/api/controller/v1/HelloWorldController.java
new file mode 100644
index 0000000..2ad49a4
--- /dev/null
+++ b/api/src/main/java/ca/bc/gov/educ/eas/api/controller/v1/HelloWorldController.java
@@ -0,0 +1,18 @@
+package ca.bc.gov.educ.eas.api.controller.v1;
+
+import ca.bc.gov.educ.eas.api.endpoint.v1.HelloWorldEndpoint;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+public class HelloWorldController implements HelloWorldEndpoint {
+
+ @Autowired
+ public HelloWorldController() {}
+
+
+ @Override
+ public String helloWorld() {
+ return "Hello World";
+ }
+}
diff --git a/api/src/main/java/ca/bc/gov/educ/eas/api/endpoint/v1/HelloWorldEndpoint.java b/api/src/main/java/ca/bc/gov/educ/eas/api/endpoint/v1/HelloWorldEndpoint.java
new file mode 100644
index 0000000..a9fb660
--- /dev/null
+++ b/api/src/main/java/ca/bc/gov/educ/eas/api/endpoint/v1/HelloWorldEndpoint.java
@@ -0,0 +1,15 @@
+package ca.bc.gov.educ.eas.api.endpoint.v1;
+
+import ca.bc.gov.educ.eas.api.constants.v1.URL;
+import io.swagger.v3.oas.annotations.responses.ApiResponse;
+import io.swagger.v3.oas.annotations.responses.ApiResponses;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+
+@RequestMapping(URL.BASE_URL + "/hello")
+public interface HelloWorldEndpoint {
+
+ @GetMapping()
+ @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK")})
+ String helloWorld();
+}
diff --git a/api/src/main/java/ca/bc/gov/educ/eas/api/exception/EasAPIRuntimeException.java b/api/src/main/java/ca/bc/gov/educ/eas/api/exception/EasAPIRuntimeException.java
new file mode 100644
index 0000000..94d6e7c
--- /dev/null
+++ b/api/src/main/java/ca/bc/gov/educ/eas/api/exception/EasAPIRuntimeException.java
@@ -0,0 +1,26 @@
+package ca.bc.gov.educ.eas.api.exception;
+
+/**
+ * The type EAS api runtime exception.
+ */
+public class EasAPIRuntimeException extends RuntimeException {
+
+ /**
+ * The constant serialVersionUID.
+ */
+ private static final long serialVersionUID = 5241655513745148898L;
+
+ /**
+ * Instantiates a new EAS api runtime exception.
+ *
+ * @param message the message
+ */
+ public EasAPIRuntimeException(String message) {
+ super(message);
+ }
+
+ public EasAPIRuntimeException(Throwable exception) {
+ super(exception);
+ }
+
+}
diff --git a/api/src/main/java/ca/bc/gov/educ/eas/api/exception/EntityNotFoundException.java b/api/src/main/java/ca/bc/gov/educ/eas/api/exception/EntityNotFoundException.java
new file mode 100644
index 0000000..4e124c1
--- /dev/null
+++ b/api/src/main/java/ca/bc/gov/educ/eas/api/exception/EntityNotFoundException.java
@@ -0,0 +1,59 @@
+package ca.bc.gov.educ.eas.api.exception;
+
+import lombok.NoArgsConstructor;
+import org.apache.commons.lang3.StringUtils;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.stream.IntStream;
+
+/**
+ * EntityNotFoundException to provide more details in error description
+ */
+@NoArgsConstructor
+public class EntityNotFoundException extends RuntimeException {
+
+ /**
+ * Instantiates a new Entity not found exception.
+ *
+ * @param clazz the clazz
+ * @param searchParamsMap the search params map
+ */
+ public EntityNotFoundException(Class clazz, String... searchParamsMap) {
+ super(EntityNotFoundException.generateMessage(clazz.getSimpleName(), toMap(String.class, String.class, searchParamsMap)));
+ }
+
+ /**
+ * Generate message string.
+ *
+ * @param entity the entity
+ * @param searchParams the search params
+ * @return the string
+ */
+ private static String generateMessage(String entity, Map searchParams) {
+ return StringUtils.capitalize(entity) +
+ " was not found for parameters " +
+ searchParams;
+ }
+
+ /**
+ * To map map.
+ *
+ * @param the type parameter
+ * @param the type parameter
+ * @param keyType the key type
+ * @param valueType the value type
+ * @param entries the entries
+ * @return the map
+ */
+ private static Map toMap(
+ Class keyType, Class valueType, Object... entries) {
+ if (entries.length % 2 == 1)
+ throw new IllegalArgumentException("Invalid entries");
+ return IntStream.range(0, entries.length / 2).map(i -> i * 2)
+ .collect(HashMap::new,
+ (m, i) -> m.put(keyType.cast(entries[i]), valueType.cast(entries[i + 1])),
+ Map::putAll);
+ }
+
+}
diff --git a/api/src/main/java/ca/bc/gov/educ/eas/api/exception/InvalidParameterException.java b/api/src/main/java/ca/bc/gov/educ/eas/api/exception/InvalidParameterException.java
new file mode 100644
index 0000000..6c7af80
--- /dev/null
+++ b/api/src/main/java/ca/bc/gov/educ/eas/api/exception/InvalidParameterException.java
@@ -0,0 +1,39 @@
+package ca.bc.gov.educ.eas.api.exception;
+
+/**
+ * InvalidParameterException to provide error details when unexpected parameters are passed to endpoint
+ *
+ */
+public class InvalidParameterException extends RuntimeException {
+
+ /**
+ * The constant serialVersionUID.
+ */
+ private static final long serialVersionUID = -2325104800954988680L;
+
+ /**
+ * Instantiates a new Invalid parameter exception.
+ *
+ * @param searchParamsMap the search params map
+ */
+ public InvalidParameterException(String... searchParamsMap) {
+ super(InvalidParameterException.generateMessage(searchParamsMap));
+ }
+
+ /**
+ * Generate message string.
+ *
+ * @param searchParams the search params
+ * @return the string
+ */
+ private static String generateMessage(String... searchParams) {
+ StringBuilder message = new StringBuilder("Unexpected request parameters provided: ");
+ String prefix = "";
+ for (String parameter : searchParams) {
+ message.append(prefix);
+ prefix = ",";
+ message.append(parameter);
+ }
+ return message.toString();
+ }
+}
diff --git a/api/src/main/java/ca/bc/gov/educ/eas/api/exception/InvalidPayloadException.java b/api/src/main/java/ca/bc/gov/educ/eas/api/exception/InvalidPayloadException.java
new file mode 100644
index 0000000..fb5515a
--- /dev/null
+++ b/api/src/main/java/ca/bc/gov/educ/eas/api/exception/InvalidPayloadException.java
@@ -0,0 +1,27 @@
+package ca.bc.gov.educ.eas.api.exception;
+
+import ca.bc.gov.educ.eas.api.exception.errors.ApiError;
+import lombok.Getter;
+
+/**
+ * The type Invalid payload exception.
+ */
+@SuppressWarnings("squid:S1948")
+public class InvalidPayloadException extends RuntimeException {
+
+ /**
+ * The Error.
+ */
+ @Getter
+ private final ApiError error;
+
+ /**
+ * Instantiates a new Invalid payload exception.
+ *
+ * @param error the error
+ */
+ public InvalidPayloadException(final ApiError error) {
+ super(error.getMessage());
+ this.error = error;
+ }
+}
diff --git a/api/src/main/java/ca/bc/gov/educ/eas/api/exception/RestExceptionHandler.java b/api/src/main/java/ca/bc/gov/educ/eas/api/exception/RestExceptionHandler.java
new file mode 100644
index 0000000..2e4f8d2
--- /dev/null
+++ b/api/src/main/java/ca/bc/gov/educ/eas/api/exception/RestExceptionHandler.java
@@ -0,0 +1,141 @@
+package ca.bc.gov.educ.eas.api.exception;
+
+import ca.bc.gov.educ.eas.api.exception.errors.ApiError;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.core.Ordered;
+import org.springframework.core.annotation.Order;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpStatusCode;
+import org.springframework.http.ResponseEntity;
+import org.springframework.http.converter.HttpMessageNotReadableException;
+import org.springframework.web.bind.MethodArgumentNotValidException;
+import org.springframework.web.bind.annotation.ControllerAdvice;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.context.request.WebRequest;
+import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
+
+import java.time.LocalDateTime;
+
+import static org.springframework.http.HttpStatus.BAD_REQUEST;
+import static org.springframework.http.HttpStatus.NOT_FOUND;
+
+/**
+ * The type Rest exception handler.
+ */
+@Order(Ordered.HIGHEST_PRECEDENCE)
+@ControllerAdvice
+public class RestExceptionHandler extends ResponseEntityExceptionHandler {
+
+ /**
+ * The constant log.
+ */
+ private static final Logger log = LoggerFactory.getLogger(RestExceptionHandler.class);
+
+ /**
+ * Handle http message not readable response entity.
+ *
+ * @param ex the ex
+ * @param headers the headers
+ * @param status the status
+ * @param request the request
+ * @return the response entity
+ */
+ @Override
+ protected ResponseEntity