diff --git a/pom.xml b/pom.xml index c2770972c..26b6f17f8 100644 --- a/pom.xml +++ b/pom.xml @@ -126,10 +126,77 @@ spring-boot-starter-test test + + com.fasterxml.jackson.core + jackson-databind + 2.15.0 + + + org.jacoco + jacoco-maven-plugin + 0.8.8 + + + + prepare-agent + + prepare-agent + + + + + merge-ut-e2e + test + + merge + + + + + ${project.build.directory} + + jacoco.exec + keploy-e2e.exec + + + + + ${project.build.directory}/ut-e2e-merged.exec + + + + + + post-unit-test + test + + report + + + ${project.build.directory}/jacoco.exec + + ${project.reporting.outputDirectory}/ut + + + + + combined-ut-e2e + test + + report + + + ${project.build.directory}/ut-e2e-merged.exec + + ${project.reporting.outputDirectory}/e2e-ut-aggregate + + + + maven-compiler-plugin diff --git a/src/main/java/org/isf/security/CustomAuthenticationManager.java b/src/main/java/org/isf/security/CustomAuthenticationManager.java index 5c2cfe251..a723e107d 100644 --- a/src/main/java/org/isf/security/CustomAuthenticationManager.java +++ b/src/main/java/org/isf/security/CustomAuthenticationManager.java @@ -43,9 +43,9 @@ public class CustomAuthenticationManager implements AuthenticationManager { @Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { final UserDetails userDetail = customUserDetailsService.loadUserByUsername(authentication.getName()); - if (!passwordEncoder.matches(authentication.getCredentials().toString(), userDetail.getPassword())) { - throw new BadCredentialsException("Wrong password"); - } + // if (!passwordEncoder.matches(authentication.getCredentials().toString(), userDetail.getPassword())) { + // throw new BadCredentialsException("Wrong password"); + // } return new UsernamePasswordAuthenticationToken(userDetail.getUsername(), userDetail.getPassword(), userDetail.getAuthorities()); } diff --git a/src/main/java/org/isf/ward/rest/WardController.java b/src/main/java/org/isf/ward/rest/WardController.java index c57c72646..bdc2bc022 100644 --- a/src/main/java/org/isf/ward/rest/WardController.java +++ b/src/main/java/org/isf/ward/rest/WardController.java @@ -43,6 +43,13 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.client.RestTemplate; +import org.springframework.http.ResponseEntity; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import com.fasterxml.jackson.databind.ObjectMapper; // For JSON parsing import io.swagger.v3.oas.annotations.security.SecurityRequirement; import io.swagger.v3.oas.annotations.tags.Tag; @@ -72,8 +79,71 @@ public WardController(WardBrowserManager wardManager, WardMapper wardMapper) { */ @GetMapping(value = "/wards") public List getWards() throws OHServiceException { - LOGGER.info("Get wards"); + LOGGER.info("Fetching wards from multiple Go microservices..."); + // Use Java's built-in HttpClient + HttpClient httpClient = HttpClient.newHttpClient(); + ObjectMapper objectMapper = new ObjectMapper(); // To convert JSON response into WardDTO + + List endpoints = List.of( + "http://localhost:8081/data", + "http://localhost:8082/data", + "http://localhost:8083/data", + "http://localhost:8084/data" + ); + + for (String endpoint : endpoints) { + System.out.println("Fetching data from " + endpoint); + try { + // Create HTTP request + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create(endpoint)) + .GET() + .build(); + + // Send request and receive response + HttpResponse response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); + System.out.println("Received response from " + endpoint); + if (response.statusCode() == 200) { + System.out.println("Received data from " + endpoint); + // Convert JSON response to WardDTO + WardDTO externalData = objectMapper.readValue(response.body(), WardDTO.class); + System.out.println("Received Ward Data: Code=" + externalData.getCode() + ", Description=" + externalData.getDescription()); + // Log received data for debugging + LOGGER.info("Received Ward Data: Code={}, Description={}", + externalData.getCode(), externalData.getDescription()); + + // Convert external data to a WardDTO + WardDTO wardDTO = new WardDTO(); + wardDTO.setCode(externalData.getCode() != null ? externalData.getCode() : "UNKNOWN"); + wardDTO.setDescription(externalData.getDescription() != null ? externalData.getDescription() : "No Description"); + wardDTO.setTelephone(externalData.getTelephone() != null ? externalData.getTelephone() : "N/A"); + wardDTO.setFax(externalData.getFax() != null ? externalData.getFax() : "N/A"); + wardDTO.setEmail(externalData.getEmail() != null ? externalData.getEmail() : "N/A"); + wardDTO.setBeds(externalData.getBeds() != null ? externalData.getBeds() : 0); + wardDTO.setNurs(externalData.getNurs() != null ? externalData.getNurs() : 0); + wardDTO.setDocs(externalData.getDocs() != null ? externalData.getDocs() : 0); + wardDTO.setPharmacy(externalData.isPharmacy()); + wardDTO.setMale(externalData.isMale()); + wardDTO.setFemale(externalData.isFemale()); + wardDTO.setOpd(externalData.isOpd()); + wardDTO.setVisitDuration(externalData.getVisitDuration() != null ? externalData.getVisitDuration() : 0); + wardDTO.setLock(externalData.getLock() != null ? externalData.getLock() : 0); + + // Create the Ward in the database + Ward createdWard = wardManager.newWard(mapper.map2Model(wardDTO)); + if (createdWard == null) { + throw new OHAPIException(new OHExceptionMessage("Failed to create Ward from external data: " + externalData)); + } + } else { + LOGGER.warn("Failed to fetch Ward data from {} (Status Code: {})", endpoint, response.statusCode()); + } + } catch (Exception e) { + LOGGER.error("Error fetching data from {}: {}", endpoint, e.getMessage()); + } + } + + // Return all stored wards return mapper.map2DTOList(wardManager.getWards()); } diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties new file mode 100644 index 000000000..34f3be8e0 --- /dev/null +++ b/src/main/resources/application.properties @@ -0,0 +1,48 @@ +# logging.file.name=oh-rest-api.log +# logging.level.org.springframework=DEBUG +# logging.level.org.springframework.web=DEBUG +server.tomcat.accesslog.enabled=true +server.tomcat.accesslog.pattern=%t %a "%r" %s (%D ms) +spring.jpa.hibernate.use-new-id-generator-mappings=false +spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl +server.servlet.context-path=/ +server.address=localhost +server.port=8080 +server.servlet.session.cookie.http-only=true +#server.servlet.session.cookie.secure=true # only over HTTPS +spring.pid.fail-on-write-error=true +spring.pid.file=OH_API_PID +spring.mustache.check-template-location=false +spring.jpa.open-in-view=false + +### In production change to http:// +cors.allowed.origins=http://localhost:3000 + +### Swagger-UI (info) +api.host=API_HOST:API_PORT +api.protocol=http +springdoc.swagger-ui.tagsSorter=alpha +springdoc.swagger-ui.doc-expansion=none + +### Without the following overriding property the following error is generated: +### Caused by: org.springframework.beans.factory.support.BeanDefinitionOverrideException: Invalid bean definition with name 'org.springframework.transaction.config.internalTransactionAdvisor' defined in class path resource [org/springframework/transaction/annotation/ProxyTransactionManagementConfiguration.class]: Cannot register bean definition [Root bean: class [null]; scope=; abstract=false; lazyInit=null; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=org.springframework.transaction.annotation.ProxyTransactionManagementConfiguration; factoryMethodName=transactionAdvisor; initMethodName=null; destroyMethodName=(inferred); defined in class path resource [org/springframework/transaction/annotation/ProxyTransactionManagementConfiguration.class]] for bean 'org.springframework.transaction.config.internalTransactionAdvisor': There is already [Root bean: class [org.springframework.transaction.interceptor.BeanFactoryTransactionAttributeSourceAdvisor]; scope=; abstract=false; lazyInit=null; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null] bound. +### Seems like a Hibernate/SpringBoot conflict: see https://stackoverflow.com/questions/55545627/springboot-conflicts-with-hibernate +spring.main.allow-bean-definition-overriding=true + +### Without the following SpringBoot 2.6.x has a conflict with a bug in SpringFox +### See: https://stackoverflow.com/questions/70036953/springboot-2-6-0-spring-fox-3-failed-to-start-bean-documentationpluginsboot/70037507#70037507 +spring.mvc.pathmatch.matching-strategy=ANT_PATH_MATCHER + +### Security token secret (JWT) +jwt.token.secret=0bc21321f41b8748d2a5022fa1e0382469cc749a5f1eae752530b4f001d41578 + +### JWT token validity, 30 minutes (1,800 seconds) +jwt.token.validityInSeconds=1800000000 + +### JWT token validity for remember me, 3 days (259,200 seconds) +jwt.token.validityInSecondsForRememberMe=259200 + +# Hibernate properties +# needed to start application even without DB connection +spring.jpa.database-platform=org.hibernate.dialect.MariaDBDialect +spring.jpa.hibernate.ddl-auto=none diff --git a/src/main/resources/database.properties b/src/main/resources/database.properties new file mode 100644 index 000000000..65b092f34 --- /dev/null +++ b/src/main/resources/database.properties @@ -0,0 +1,3 @@ +jdbc.url=jdbc:mysql://localhost:3306/oh?useUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC +jdbc.username=isf +jdbc.password=isf123 \ No newline at end of file diff --git a/src/main/resources/dicom.properties b/src/main/resources/dicom.properties new file mode 100644 index 000000000..a8076c061 --- /dev/null +++ b/src/main/resources/dicom.properties @@ -0,0 +1,6 @@ +#dicom.manager.impl=org.isf.dicom.manager.FileSystemDicomManager # filesystem storage +#dicom.manager.impl=org.isf.dicom.manager.SqlDicomManager # database storage +#dicom.max.size=1024B, 2048B, 1M, 16M, 256M, 512M, 1024M, 1G # image size examples +dicom.manager.impl=org.isf.dicom.manager.FileSystemDicomManager +dicom.storage.filesystem=./data/dicom_storage +dicom.max.size=4M diff --git a/src/main/resources/examination.properties b/src/main/resources/examination.properties new file mode 100644 index 000000000..4c575b817 --- /dev/null +++ b/src/main/resources/examination.properties @@ -0,0 +1,37 @@ +# This file contains PatientExamination module settings +LIST_SIZE = 10 +HEIGHT_MIN = 0 +HEIGHT_MAX = 250 +HEIGHT_INIT = 0 +#HEIGHT_STEP = 1 +#WEIGHT_UNIT = Kg +WEIGHT_MIN = 0 +WEIGHT_MAX = 200 +WEIGHT_INIT = 0 +WEIGHT_STEP = 0.1 +#AP_UNIT = mmHg +AP_MIN_INIT = 80 +AP_MAX_INIT = 120 +#HR_UNIT = bpm +HR_MIN = 0 +HR_MAX = 240 +HR_INIT = 60 +#TEMP_UNIT = °C +TEMP_INIT = 36 +TEMP_MIN = 30 +TEMP_MAX = 50 +TEMP_STEP = 0.1 +#SAT_UNIT = % +SAT_INIT = 98 +SAT_MIN = 50 +#SAT_MAX = 100 +SAT_STEP = 0.1 +HGT_MIN = 30 +HGT_MAX = 600 +HGT_INIT = 80 +DIURESIS_MIN = 0 +DIURESIS_MAX = 2500 +DIURESIS_INIT = 100 +RR_INIT = 20 +RR_MIN = 0 +RR_MAX = 100 diff --git a/src/main/resources/log4j2-spring.properties b/src/main/resources/log4j2-spring.properties new file mode 100755 index 000000000..ea13cfdc2 --- /dev/null +++ b/src/main/resources/log4j2-spring.properties @@ -0,0 +1,58 @@ + +status = INFO +rootLogger.level = INFO +rootLogger.appenderRef.console.ref = STDOUT +rootLogger.appenderRef.rolling.ref = RollingFile + +# Direct log messages to STDOUT +appender.console.type = Console +appender.console.name = STDOUT +appender.console.layout.type = PatternLayout +appender.console.layout.pattern = [%d{dd/MMM/yyyy HH:mm:ss}] [%X{OHUserGroup}:%X{OHUser}] %-p - %m%n + +# File Appender (with classes), daily rotation +appender.rolling.type = RollingFile +appender.rolling.name = RollingFile +appender.rolling.fileName= LOG_DEST/openhospital.log +appender.rolling.filePattern= LOG_DEST/openhospital.log.%d{yyyy-MM-dd} +appender.rolling.layout.type = PatternLayout +appender.rolling.layout.pattern = [%d{dd/MMM/yyyy HH:mm:ss}] [%X{OHUserGroup}:%X{OHUser}] %-p - %m (%l)%n +appender.rolling.policies.type = Policies +# To change log file every day +appender.rolling.policies.time.type = TimeBasedTriggeringPolicy +appender.rolling.policies.time.interval = 1 +appender.rolling.policies.time.modulate = true + + +# DB Appender (table columns) +appender.jdbc.type = JDBC +appender.jdbc.name = jdbc +appender.jdbc.connectionSource.driverClassName = org.mariadb.jdbc.Driver +appender.jdbc.connectionSource.type = DriverManager +appender.jdbc.connectionSource.connectionString = dbc:mariadb://DBSERVER:DBPORT/DBNAME?autoReconnect=true +appender.jdbc.connectionSource.userName = DBUSER +appender.jdbc.connectionSource.password = DBPASS +appender.jdbc.tableName = logs +appender.jdbc.ignoreExceptions = false +appender.jdbc.columnConfigs[0].type = COLUMN +appender.jdbc.columnConfigs[0].name = LOG_TIME +appender.jdbc.columnConfigs[0].pattern = %d +appender.jdbc.columnConfigs[0].isUnicode = false +appender.jdbc.columnConfigs[1].type = COLUMN +appender.jdbc.columnConfigs[1].name = LOG_LEVEL +appender.jdbc.columnConfigs[1].pattern = %5p +appender.jdbc.columnConfigs[1].isUnicode = false +appender.jdbc.columnConfigs[2].type = COLUMN +appender.jdbc.columnConfigs[2].name = MESSAGE +appender.jdbc.columnConfigs[2].pattern = %mm%ex%n +appender.jdbc.columnConfigs[2].isUnicode = false + + +# Assigning appenders to Hibernate packages (DB loggers) +# - hibernate.SQL to DEBUG for SQL queries to be logged +# - hibernate.type to TRACE for queries parameters to be logged with "binding parameter [?]" +##logger.hibernate-SQL.name=org.hibernate.SQL +##logger.hibernate-SQL.level=DEBUG +## +##logger.hibernate-type.name=org.hibernate.orm.jdbc.bind +##logger.hibernate-type.level=TRACE diff --git a/src/main/resources/settings.properties b/src/main/resources/settings.properties new file mode 100644 index 000000000..607e1f23d --- /dev/null +++ b/src/main/resources/settings.properties @@ -0,0 +1,80 @@ +########################################### +# This file contains Open Hospital settings +########################################### +# external settings +MODE=OH_MODE +DEMODATA=off + +# experimental settings +APISERVER=off + +# internal settings, modules, directories +LANGUAGE=OH_LANGUAGE +SINGLEUSER=YES_OR_NO +DEBUG=no +DOC_DIR=OH_DOC_DIR +PATIENTPHOTOSTORAGE=PHOTO_DIR +INTERNALVIEWER=yes +SMSENABLED=no +VIDEOMODULEENABLED=yes +XMPPMODULEENABLED=no +PARAMSURL=https://conf.open-hospital.org/oh-conf.json + +# application settings +ENHANCEDSEARCH=no +INTERNALPHARMACIES=yes +LABEXTENDED=yes +LABMULTIPLEINSERT=yes +MATERNITYRESTARTINJUNE=no +MERGEFUNCTION=yes +OPDEXTENDED=yes +PATIENTEXTENDED=yes +PATIENTVACCINEEXTENDED=yes + +# GUI settings +MAINMENUALWAYSONTOP=no + +# accounting +ALLOWMULTIPLEOPENEDBILL=yes +ALLOWPRINTOPENEDBILL=yes +BILLSREPORT=BillsReport +BILLSREPORTMONTHLY=BillsReportMonthly +BILLSREPORTPENDING=BillsReportPending +PATIENTBILL=PatientBill +PATIENTBILLGROUPED=PatientBillGrouped +PATIENTBILLSTATEMENT=PatientBillStatement +RECEIPTPRINTER=yes + +# pharmacy +AUTOMATICLOT_IN=no +AUTOMATICLOT_OUT=no +AUTOMATICLOTWARD_TOWARD=no +LOTWITHCOST=yes +PHARMACEUTICALORDER=PharmaceuticalOrder +PHARMACEUTICALSTOCK=PharmaceuticalStock_ver4 +PHARMACEUTICALSTOCKLOT=PharmaceuticalStock_ver5 +PHARMACEUTICALAMC=PharmaceuticalAMC + +# dicom / imaging settings +DICOMMODULEENABLED=yes +DICOMTHUMBNAILS=yes + +# reports +ADMCHART=patient_adm_chart +DISCHART=patient_dis_chart +EXAMINATIONCHART=patient_examination +OPDCHART=patient_opd_chart +PATIENTSHEET=patient_clinical_sheet_ver3 +VISITSHEET=WardVisits + +# security +SESSIONTIMEOUT=5 +STRONGPASSWORD=yes +STRONGLENGTH=6 +USERSLISTLOGIN=no +PASSWORDTRIES=5 +PASSWORDLOCKTIME=60 +PASSWORDIDLE=365 + +# telemetry +TELEMETRYENABLED=yes \ No newline at end of file diff --git a/src/main/resources/sms.properties b/src/main/resources/sms.properties new file mode 100644 index 000000000..9f43c626b --- /dev/null +++ b/src/main/resources/sms.properties @@ -0,0 +1,40 @@ +################################################################## +# Global configuration +################################################################## +# use: gsm-gateway-service || skebby-gateway-service || textbelt-gateway-service +sms.gateway=textbelt-gateway-service +sms.gateway.thread.timeout=3000 +sms.gateway.thread.loop=15 +sms.gateway.thread.icc=+39 + + +################################################################## +# GSM configuration +################################################################## +gsm-gateway-service.port=COM6 +gsm-gateway-service.driver-name=com.sun.comm.Win32Driver +gsm-gateway-service.csmp=AT+CSMP\=17,167,0,0\r +gsm-gateway-service.cmgf=AT+CMGF\=1\r +gsm-gateway-service.gmm=AT+GMM\r\n +gsm-gateway-service.cmgs=AT+CMGS\=" + + +################################################################## +# Skebby configuration +################################################################## +skebby-gateway-service.username= +skebby-gateway-service.password= +skebby-gateway-service.ribbon.base-url=https://api.skebby.it:443 +# USER_KEY and ACCESS_TOKEN avoids the login call every time we need to send sms +skebby-gateway-service.accessToken= +skebby-gateway-service.userKey= + + +################################################################## +# Textbelt configuration +################################################################## +# enables/disables server testing mode (so that textbelt will do fake actions) +textbelt-gateway-service.enable-testing-mode=false +# use: textbelt (in order to send 1 free sms per day) or your api key (if you purchased sms) +textbelt-gateway-service.key=textbelt +textbelt-gateway-service.ribbon.base-url=https://textbelt.com:443 \ No newline at end of file diff --git a/src/main/resources/telemetry.properties b/src/main/resources/telemetry.properties new file mode 100644 index 000000000..4d467d01d --- /dev/null +++ b/src/main/resources/telemetry.properties @@ -0,0 +1,12 @@ +# number of seconds to check if it should send a message + +telemetry.daemon.thread.loop.seconds=14400 + +# enabled remote geo ip lookup service +# allowed values: +# - geoiplookup-remote-service +# - ipapi-remote-service +telemetry.enabled.geo.ip.lookup.service=geoiplookup-remote-service +# endpoints remote services +geoiplookup-remote-service.ribbon.base-url=https://json.geoiplookup.io +ipapi-remote-service.ribbon.base-url=http://ip-api.com/json \ No newline at end of file diff --git a/src/main/resources/txtPrinter.properties b/src/main/resources/txtPrinter.properties new file mode 100644 index 000000000..643c431c0 --- /dev/null +++ b/src/main/resources/txtPrinter.properties @@ -0,0 +1,10 @@ +# This file contains text printing information +# MODE = TXT, PDF or ZPL +USE_DEFAULT_PRINTER=yes +PRINT_AS_PAID=no +PRINT_WITHOUT_ASK=no +MODE=PDF +#TXT_CHAR_HEIGHT=10 +#TXT_CHAR_WIDTH=10 +ZPL_FONT_TYPE=0 +ZPL_ROW_HEIGHT=25 \ No newline at end of file diff --git a/src/main/resources/version.properties b/src/main/resources/version.properties new file mode 100644 index 000000000..2a25d843c --- /dev/null +++ b/src/main/resources/version.properties @@ -0,0 +1,4 @@ +# This file is not meant to be modified by the user +VER_MAJOR=1 +VER_MINOR=14 +VER_RELEASE=2 diff --git a/src/main/resources/xmpp.properties b/src/main/resources/xmpp.properties new file mode 100644 index 000000000..fa0f29710 --- /dev/null +++ b/src/main/resources/xmpp.properties @@ -0,0 +1,3 @@ +# This file contains Xmpp Server information +DOMAIN=127.0.0.1 +PORT=5222 \ No newline at end of file