From d76763acd7c228e907c6673b3e781501520d20db Mon Sep 17 00:00:00 2001 From: David E Jones Date: Thu, 7 Dec 2023 13:11:35 -0600 Subject: [PATCH 01/18] Add sentDate field to WikiBlogCategory entity --- framework/entity/ResourceEntities.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/framework/entity/ResourceEntities.xml b/framework/entity/ResourceEntities.xml index f9ac9b8cc..e975c91ef 100644 --- a/framework/entity/ResourceEntities.xml +++ b/framework/entity/ResourceEntities.xml @@ -186,6 +186,7 @@ along with this software (see the LICENSE.md file). If not, see + The date/time a blog post within a category was sent by email or other means. From 62b98b28438013e6bf6353da39b9ae84250c5a1d Mon Sep 17 00:00:00 2001 From: David E Jones Date: Tue, 19 Dec 2023 13:43:50 -0600 Subject: [PATCH 02/18] Library updates: Jetty to 10.0.18, Log4J to 2.22.0, H2 to 2.2.224, commons-lang3, commons-io, commons-logging, commons-validator, jackson-databind to 2.16.0, jsoup, shiro --- framework/build.gradle | 52 +++++++++++++++++++++--------------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/framework/build.gradle b/framework/build.gradle index 65137c15c..15616ccb5 100644 --- a/framework/build.gradle +++ b/framework/build.gradle @@ -27,7 +27,7 @@ buildscript { maven { url "https://plugins.gradle.org/m2/" } } dependencies { - classpath 'com.github.ben-manes:gradle-versions-plugin:0.47.0' + classpath 'com.github.ben-manes:gradle-versions-plugin:0.50.0' // uncomment to add the Error Prone compiler: classpath 'net.ltgt.gradle:gradle-errorprone-plugin:0.0.8' } } @@ -96,15 +96,15 @@ dependencies { api 'org.apache.commons:commons-csv:1.10.0' // Apache 2.0 // NOTE: commons-email depends on com.sun.mail:javax.mail, included below, so use module() here to not get dependencies api module('org.apache.commons:commons-email:1.5') // Apache 2.0 - api 'org.apache.commons:commons-lang3:3.13.0' // Apache 2.0; used by cron-utils + api 'org.apache.commons:commons-lang3:3.14.0' // Apache 2.0; used by cron-utils api 'commons-beanutils:commons-beanutils:1.9.4' // Apache 2.0 api 'commons-codec:commons-codec:1.16.0' // Apache 2.0 api 'commons-collections:commons-collections:3.2.2' // Apache 2.0 api 'commons-digester:commons-digester:2.1' // Apache 2.0 api 'commons-fileupload:commons-fileupload:1.5' // Apache 2.0 - api 'commons-io:commons-io:2.13.0' // Apache 2.0 - api 'commons-logging:commons-logging:1.2' // Apache 2.0 - api 'commons-validator:commons-validator:1.7' // Apache 2.0 + api 'commons-io:commons-io:2.15.1' // Apache 2.0 + api 'commons-logging:commons-logging:1.3.0' // Apache 2.0 + api 'commons-validator:commons-validator:1.8.0' // Apache 2.0 // Cron Utils api 'com.cronutils:cron-utils:9.2.1' // Apache 2.0 @@ -119,7 +119,7 @@ dependencies { api 'org.freemarker:freemarker:2.3.32' // Apache 2.0 // H2 Database - api 'com.h2database:h2:2.2.222' // MPL 2.0, EPL 1.0 + api 'com.h2database:h2:2.2.224' // MPL 2.0, EPL 1.0 // Java Specifications api 'javax.transaction:jta:1.1' @@ -144,11 +144,11 @@ dependencies { api 'com.beust:jcommander:1.82' // Jackson Databind (JSON, etc) - api 'com.fasterxml.jackson.core:jackson-databind:2.15.2' + api 'com.fasterxml.jackson.core:jackson-databind:2.16.0' // Jetty HTTP Client and Proxy Servlet - api 'org.eclipse.jetty:jetty-client:10.0.16' // Apache 2.0 - api 'org.eclipse.jetty:jetty-proxy:10.0.16' // Apache 2.0 + api 'org.eclipse.jetty:jetty-client:10.0.18' // Apache 2.0 + api 'org.eclipse.jetty:jetty-proxy:10.0.18' // Apache 2.0 // javax.mail // NOTE: javax.mail depends on 'javax.activation:activation' which is the old package for 'javax.activation:javax.activation-api' used by jaxb-api @@ -158,18 +158,18 @@ dependencies { api 'joda-time:joda-time:2.12.5' // Apache 2.0 // JSoup (HTML parser, cleaner) - api 'org.jsoup:jsoup:1.16.1' // MIT + api 'org.jsoup:jsoup:1.17.1' // MIT // Apache Shiro - api module('org.apache.shiro:shiro-core:1.12.0') // Apache 2.0 - api module('org.apache.shiro:shiro-web:1.12.0') // Apache 2.0 + api module('org.apache.shiro:shiro-core:1.13.0') // Apache 2.0 + api module('org.apache.shiro:shiro-web:1.13.0') // Apache 2.0 // SLF4J, Log4j 2 (note Log4j 2 is used by various libraries, best not to replace it even if mostly possible with SLF4J) api 'org.slf4j:slf4j-api:2.0.9' - implementation 'org.apache.logging.log4j:log4j-core:2.20.0' - implementation 'org.apache.logging.log4j:log4j-api:2.20.0' - runtimeOnly 'org.apache.logging.log4j:log4j-jcl:2.20.0' - runtimeOnly 'org.apache.logging.log4j:log4j-slf4j2-impl:2.20.0' + implementation 'org.apache.logging.log4j:log4j-core:2.22.0' + implementation 'org.apache.logging.log4j:log4j-api:2.22.0' + runtimeOnly 'org.apache.logging.log4j:log4j-jcl:2.22.0' + runtimeOnly 'org.apache.logging.log4j:log4j-slf4j2-impl:2.22.0' // SubEtha SMTP (module as depends on old javax.mail location; also uses SLF4J, activation included elsewhere) api module('org.subethamail:subethasmtp:3.1.7') @@ -190,11 +190,11 @@ dependencies { // ========== test dependencies ========== // junit-platform-launcher is a dependency from spock-core, included explicitly to get more recent version as needed - testImplementation 'org.junit.platform:junit-platform-launcher:1.10.0' + testImplementation 'org.junit.platform:junit-platform-launcher:1.10.1' // junit-platform-suite required for test suites to specify test class order, etc - testImplementation 'org.junit.platform:junit-platform-suite:1.10.0' + testImplementation 'org.junit.platform:junit-platform-suite:1.10.1' // junit-jupiter-api for using JUnit directly, not generally needed for Spock based tests - testImplementation 'org.junit.jupiter:junit-jupiter-api:5.10.0' + testImplementation 'org.junit.jupiter:junit-jupiter-api:5.10.1' // Spock Framework testImplementation platform("org.spockframework:spock-bom:2.1-groovy-3.0") // Apache 2.0 testImplementation 'org.spockframework:spock-core:2.1-groovy-3.0' // Apache 2.0 @@ -203,16 +203,16 @@ dependencies { // ========== executable war dependencies ========== // Jetty - execWarRuntimeOnly 'org.eclipse.jetty:jetty-server:10.0.16' // Apache 2.0 - execWarRuntimeOnly 'org.eclipse.jetty:jetty-webapp:10.0.16' // Apache 2.0 - execWarRuntimeOnly 'org.eclipse.jetty:jetty-jndi:10.0.16' // Apache 2.0 - execWarRuntimeOnly 'org.eclipse.jetty.websocket:websocket-javax-server:10.0.16' // Apache 2.0 - execWarRuntimeOnly ('org.eclipse.jetty.websocket:websocket-javax-client:10.0.16') { // Apache 2.0 + execWarRuntimeOnly 'org.eclipse.jetty:jetty-server:10.0.18' // Apache 2.0 + execWarRuntimeOnly 'org.eclipse.jetty:jetty-webapp:10.0.18' // Apache 2.0 + execWarRuntimeOnly 'org.eclipse.jetty:jetty-jndi:10.0.18' // Apache 2.0 + execWarRuntimeOnly 'org.eclipse.jetty.websocket:websocket-javax-server:10.0.18' // Apache 2.0 + execWarRuntimeOnly ('org.eclipse.jetty.websocket:websocket-javax-client:10.0.18') { // Apache 2.0 exclude group: 'javax.websocket' } // we have the full websocket API, including the client one causes problems execWarRuntimeOnly 'javax.websocket:javax.websocket-api:1.1' - execWarRuntimeOnly ('org.eclipse.jetty.websocket:websocket-jetty-server:10.0.16') // Apache 2.0 + execWarRuntimeOnly ('org.eclipse.jetty.websocket:websocket-jetty-server:10.0.18') // Apache 2.0 // only include this if using Endpoint and MessageHandler annotations: - // execWarRuntime ('org.eclipse.jetty:jetty-annotations:10.0.16') // Apache 2.0 + // execWarRuntime ('org.eclipse.jetty:jetty-annotations:10.0.18') // Apache 2.0 execWarRuntimeOnly 'org.apache.logging.log4j:log4j-slf4j18-impl:2.18.0' } From 22f3a16cd61fbd40fb7dde6a617af44c5ad37876 Mon Sep 17 00:00:00 2001 From: Taher Alkhateeb Date: Mon, 8 Jan 2024 15:07:10 +0300 Subject: [PATCH 03/18] create a service to unify login attempts --- .../service/org/moqui/impl/UserServices.xml | 47 ++++++++++++++++++- 1 file changed, 46 insertions(+), 1 deletion(-) diff --git a/framework/service/org/moqui/impl/UserServices.xml b/framework/service/org/moqui/impl/UserServices.xml index 6b433bba6..47e154f65 100644 --- a/framework/service/org/moqui/impl/UserServices.xml +++ b/framework/service/org/moqui/impl/UserServices.xml @@ -22,6 +22,51 @@ along with this software (see the LICENSE.md file). If not, see + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Authentication code is not valid + + + + + + + + + + + @@ -377,7 +422,7 @@ along with this software (see the LICENSE.md file). If not, see - A reset password was sent by email to ${userAccount.emailAddress}. This password may only be used to change your password. Your current password is still valid. + A reset password was sent to the email of username ${userAccount.username}. This password may only be used to change your password. Your current password is still valid. You must change your password before login. From f1f5f05169289ba7a79d1bb2daa1bf85af4111b2 Mon Sep 17 00:00:00 2001 From: David E Jones Date: Wed, 10 Jan 2024 14:37:43 -0600 Subject: [PATCH 04/18] Follow up on PR #628: formatting changes only to reduce indentation and better match convention used elsewhere, no functional changes --- .../service/org/moqui/impl/UserServices.xml | 49 +++++++------------ 1 file changed, 19 insertions(+), 30 deletions(-) diff --git a/framework/service/org/moqui/impl/UserServices.xml b/framework/service/org/moqui/impl/UserServices.xml index 47e154f65..a82c80322 100644 --- a/framework/service/org/moqui/impl/UserServices.xml +++ b/framework/service/org/moqui/impl/UserServices.xml @@ -33,36 +33,25 @@ along with this software (see the LICENSE.md file). If not, see - - - - - - - - - - - - - - - - - - - - - Authentication code is not valid - - - - - - - + + + + + + + + + + + + + + Authentication code is not valid + + + + From ba5fa64a9247aafd330b6e743003055f00ff2764 Mon Sep 17 00:00:00 2001 From: AyanF Date: Wed, 7 Feb 2024 12:02:10 +0530 Subject: [PATCH 05/18] Re-ordered Console Appender in log4j2.xml --- framework/src/main/resources/log4j2.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/framework/src/main/resources/log4j2.xml b/framework/src/main/resources/log4j2.xml index 78692f3bc..c31011e18 100644 --- a/framework/src/main/resources/log4j2.xml +++ b/framework/src/main/resources/log4j2.xml @@ -35,15 +35,15 @@ https://logging.apache.org/log4j/2.x/manual/layouts.html#PatternLayout + + + - - - From d750346d92f77c2f50ed1ee7926732187382a61b Mon Sep 17 00:00:00 2001 From: acetousk Date: Fri, 16 Feb 2024 16:00:41 -0700 Subject: [PATCH 06/18] Add screen resource type for a footer script --- framework/entity/ScreenEntities.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/framework/entity/ScreenEntities.xml b/framework/entity/ScreenEntities.xml index 7f04eb37f..cc296a3e1 100644 --- a/framework/entity/ScreenEntities.xml +++ b/framework/entity/ScreenEntities.xml @@ -139,6 +139,7 @@ along with this software (see the LICENSE.md file). If not, see + From f264e9e2b7ba2d20b8b25052b148fc5df4c09685 Mon Sep 17 00:00:00 2001 From: David E Jones Date: Sat, 17 Feb 2024 13:37:25 -0600 Subject: [PATCH 07/18] Add xolvegroup/WorkManagement, corrected branch on xolvegroup/Sales in addons.xml --- addons.xml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/addons.xml b/addons.xml index f5d4f2aac..2e40d7711 100644 --- a/addons.xml +++ b/addons.xml @@ -95,7 +95,8 @@ - + + + diff --git a/build.gradle b/build.gradle index 3fc65c0e6..ac7629e5c 100644 --- a/build.gradle +++ b/build.gradle @@ -851,6 +851,179 @@ task getComponent { getComponentTop(curLocationType) } } +task createComponent { + description "Create a new component. Set new component name with -Pcomponent=new_component_name (based on the moqui start component here: https://github.com/moqui/start)" + doLast { + String curLocationType = file('.git').exists() ? 'git' : 'current' + if (project.hasProperty('locationType')) curLocationType = locationType + + if (project.hasProperty('component')) { + checkRuntimeDirAndDefaults(curLocationType) + Set compsChecked = new TreeSet() + + def startComponentName = 'start' + + File componentDir = getComponent(startComponentName, curLocationType, parseAddons(), parseMyaddons(), compsChecked) + if (componentDir?.exists()) { + logger.lifecycle("Got component start, dependent components checked: ${compsChecked}") + + def newComponent = file("runtime/component/${component}") + def renameSuccessful = componentDir.renameTo(newComponent) + if (!renameSuccessful) { + logger.error("Failed to rename component start to ${component}. Try removing the existing component directory first or giving this program write permissions.") + } else { + logger.lifecycle("Renamed component start to ${component}") + } + + print "Updated file: " + newComponent.eachFileRecurse(groovy.io.FileType.FILES) { file -> + try { + // If file name is startComponentName.* rename to component.* + if (file.name.startsWith(startComponentName)) { + String newFileName = (file.name - startComponentName) + newFileName = component + newFileName + File newFile = new File(file.parent, newFileName) + file.renameTo(newFile) + file = newFile + print "${file.path - newComponent.path - '/'}, " + } + + String content = file.text + if (content.contains(startComponentName)) { + content = content.replaceAll(startComponentName, component) + file.text = content + print "${file.path - newComponent.path - '/'}, " + } + } catch (IOException e) { + println "Error processing file ${file.path}: ${e.message}" + } + } + print "\n\n" + println "Select rest api (r), screens (s), or both (B):" + def componentInput = System.in.newReader().readLine() + logger.warn("componentInput: ${componentInput}") + logger.warn("componentInput: ${componentInput == ''}") + + if (componentInput == 'r') { + new File(newComponent, 'screen').deleteDir() + new File(newComponent, 'template').deleteDir() + new File(newComponent, 'data/AppSeedData.xml').delete() + println "Selected rest api so, deleted screen, template, and AppSeedData.xml\n" + } else if (componentInput == 's') { + new File(newComponent, "services/${component}.rest.xml").delete() + new File(newComponent, 'data/ApiSeedData.xml').delete() + println "Selected screens so, deleted rest api and ApiSeedData.xml\n" + } else if (componentInput == 'b' || componentInput == 'B' || componentInput == '') { + println "Selected both rest api and screens\n" + } else { + println "Invalid input. Try again" + newComponent.deleteDir() + return + } + + println "Are you going to code or test in groovy or java [y/N]" + def codeInput = System.in.newReader().readLine() + + if (codeInput == 'y' || codeInput == 'Y') { + println "Keeping src folder\n" + } else if (codeInput == 'n' || codeInput == 'N' || codeInput == '') { + new File(newComponent, 'src').deleteDir() + new File(newComponent, 'build.grade').delete() + println "Selected no so, deleted src and build.grade\n" + } else { + println "Invalid input. Try again" + newComponent.deleteDir() + return + } + + println "Setup a git repository [Y/n]" + def gitInput = System.in.newReader().readLine() + if (gitInput == 'y' || gitInput == 'Y' || gitInput == '') { + new File(newComponent, '.git').deleteDir() + // Setup git repository + + def grgit = Grgit.init(dir: newComponent.path) + grgit.add(patterns: ['.']) +// grgit.commit(message: 'Initial commit', sign: false) + println "Selected yes, so git is initialized\n" +// println "To push to remote repository, type the git remote url or enter to skip" +// def remoteUrl = System.in.newReader().readLine() +// if (remoteUrl != '') { +// grgit.remote.add(name: 'origin', url: remoteUrl) +// def branch = grgit.branch.current().name +// grgit.push(remote: 'origin', refsOrSpecs: [':refs/branch/'+branch]) +// } + } else if (gitInput == 'n' || gitInput == 'N') { + new File(newComponent, '.git').deleteDir() + println "Selected no, so git is not initialized\n" + println "Run the following to initialize git repository:\ncd runtime/component/${component} && git init && git add . && git commit -m 'Initial commit' && cd ../../.." + } else { + println "Invalid input. Try again" + newComponent.deleteDir() + return + } + + println "Add to myaddons.xml [Y/n]" + def myaddonsInput = System.in.newReader().readLine() + if (myaddonsInput == 'y' || myaddonsInput == 'Y' || myaddonsInput == '') { + def myaddonsFile = file('myaddons.xml') + if (myaddonsFile.exists()){ + // Iterate through myaddons file and delete the lines that are + def iterator = myaddonsFile.readLines().iterator() + + while (iterator.hasNext()) { + def line = iterator.next() + if (line.contains("")) { + iterator.remove() + } + } + } else { + println "myaddons.xml not found. Creating one\nEnter repository github (g), github-ssh (GS), bitbucket (b), or bitbucket-ssh (bs)" + def repositoryInput = System.in.newReader().readLine() + myaddonsFile.append("") + } + + println "Enter the component git repository group" + def groupInput = System.in.newReader().readLine() + + println "Enter the component git repository name" + def nameInput = System.in.newReader().readLine() + + // get git branch + def grgit = Grgit.open(dir: newComponent.path) + def branch = grgit.branch.current().name + + myaddonsFile.append("\n ") + myaddonsFile.append("\n") + + } else if (myaddonsInput == 'n' || myaddonsInput == 'N') { + println "Selected no, so component not added to myaddons.xml\n" + } else { + println "Invalid input. Try again" + newComponent.deleteDir() + return + } + + } + } else { + throw new InvalidUserDataException("No component property specified") + } + + } +} task getCurrent { description "Get the current archive for a component, also check each component it depends on and if not present get its current archive; requires component property" doLast { getComponentTop('current') } From e8eec0570fbde947d74f11de0b0571cdefa742d4 Mon Sep 17 00:00:00 2001 From: acetousk Date: Thu, 11 Jul 2024 16:37:39 -0600 Subject: [PATCH 12/18] Improve git next step messages --- build.gradle | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/build.gradle b/build.gradle index ac7629e5c..1193a4f0d 100644 --- a/build.gradle +++ b/build.gradle @@ -944,19 +944,21 @@ task createComponent { def grgit = Grgit.init(dir: newComponent.path) grgit.add(patterns: ['.']) -// grgit.commit(message: 'Initial commit', sign: false) + // Can't get signing to work easily. If signing works well then might as well commit +// grgit.commit(message: 'Initial commit') println "Selected yes, so git is initialized\n" -// println "To push to remote repository, type the git remote url or enter to skip" -// def remoteUrl = System.in.newReader().readLine() -// if (remoteUrl != '') { -// grgit.remote.add(name: 'origin', url: remoteUrl) -// def branch = grgit.branch.current().name -// grgit.push(remote: 'origin', refsOrSpecs: [':refs/branch/'+branch]) -// } + println "To setup the git remote origin, type the git remote url or enter to skip" + def remoteUrl = System.in.newReader().readLine() + if (remoteUrl != '') { + grgit.remote.add(name: 'origin', url: remoteUrl) + println "Run the following to push the git repository:\ncd runtime/component/${component} && git commit -m 'Initial commit' && git push && cd ../../.." + } else { + println "Run the following to push the git repository:\ncd runtime/component/${component} && git commit -m 'Initial commit' && git remote add origin git@github.com:yourgroup/${component} && git push && cd ../../.." + } } else if (gitInput == 'n' || gitInput == 'N') { new File(newComponent, '.git').deleteDir() println "Selected no, so git is not initialized\n" - println "Run the following to initialize git repository:\ncd runtime/component/${component} && git init && git add . && git commit -m 'Initial commit' && cd ../../.." + println "Run the following to push the git repository:\ncd runtime/component/${component} && git commit -m 'Initial commit' && git remote add origin git@github.com:yourgroup/${component} && git push && cd ../../.." } else { println "Invalid input. Try again" newComponent.deleteDir() From c0d8f4c3852031c52c3f4f262019e1e1a229ed5f Mon Sep 17 00:00:00 2001 From: acetousk Date: Thu, 11 Jul 2024 16:53:24 -0600 Subject: [PATCH 13/18] Update build.gradle --- build.gradle | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/build.gradle b/build.gradle index 1193a4f0d..1d8aa0bc3 100644 --- a/build.gradle +++ b/build.gradle @@ -971,14 +971,14 @@ task createComponent { def myaddonsFile = file('myaddons.xml') if (myaddonsFile.exists()){ // Iterate through myaddons file and delete the lines that are - def iterator = myaddonsFile.readLines().iterator() + // Read the lines from the file + def lines = myaddonsFile.readLines() - while (iterator.hasNext()) { - def line = iterator.next() - if (line.contains("")) { - iterator.remove() - } - } + // Filter out the lines that contain + def filteredLines = lines.findAll { !it.contains("") } + + // Write the filtered lines back to the file + myaddonsFile.text = filteredLines.join('\n') } else { println "myaddons.xml not found. Creating one\nEnter repository github (g), github-ssh (GS), bitbucket (b), or bitbucket-ssh (bs)" def repositoryInput = System.in.newReader().readLine() From e4e666379a7143e7fe9c7a11b052a8a1bd3e8430 Mon Sep 17 00:00:00 2001 From: acetousk Date: Thu, 11 Jul 2024 22:15:42 -0600 Subject: [PATCH 14/18] Fix MoquiConf.xml generation --- build.gradle | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index 1d8aa0bc3..eec1d0346 100644 --- a/build.gradle +++ b/build.gradle @@ -901,13 +901,17 @@ task createComponent { print "\n\n" println "Select rest api (r), screens (s), or both (B):" def componentInput = System.in.newReader().readLine() - logger.warn("componentInput: ${componentInput}") - logger.warn("componentInput: ${componentInput == ''}") if (componentInput == 'r') { new File(newComponent, 'screen').deleteDir() new File(newComponent, 'template').deleteDir() new File(newComponent, 'data/AppSeedData.xml').delete() + new File(newComponent, 'MoquiConf.xml').delete() + def moquiConf = new File(newComponent, 'MoquiConf.xml') + moquiConf.append("\n" + + "\n" + + "\n" + + "") println "Selected rest api so, deleted screen, template, and AppSeedData.xml\n" } else if (componentInput == 's') { new File(newComponent, "services/${component}.rest.xml").delete() From 29f6f6e852b55d593aa818121235a5113af9ebfc Mon Sep 17 00:00:00 2001 From: acetousk Date: Wed, 31 Jul 2024 15:42:26 -0600 Subject: [PATCH 15/18] Add acme example with postgres, fix Dockerfile generation, and add lines to .gitignore --- .gitignore | 11 ++ docker/moqui-acme-postgres.yml | 187 +++++++++++++++++++++++++++++++++ docker/simple/Dockerfile | 4 +- 3 files changed, 200 insertions(+), 2 deletions(-) create mode 100644 docker/moqui-acme-postgres.yml diff --git a/.gitignore b/.gitignore index 0df5130c9..c5a7289d2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ + # gradle/build files build .gradle @@ -14,6 +15,16 @@ build /docker/db /docker/elasticsearch/data/nodes /docker/opensearch/data/nodes +/docker/acme.sh +/docker/nginx/conf.d +/docker/nginx/vhost.d +/docker/nginx/html +## Do not want to accidentally commit production certificates https://www.theregister.com/2024/07/25/data_from_deleted_github_repos/ +/docker/certs +!/docker/certs/moqui1.local.* +!/docker/certs/moqui2.local.* +!/docker/certs/moqui.local.* +!/docker/certs/README # IntelliJ IDEA files .idea diff --git a/docker/moqui-acme-postgres.yml b/docker/moqui-acme-postgres.yml new file mode 100644 index 000000000..c63b941db --- /dev/null +++ b/docker/moqui-acme-postgres.yml @@ -0,0 +1,187 @@ +# A Docker Compose application with Moqui, Postgres, OpenSearch, OpenSearch Dashboards, and virtual hosting through +# nginx-proxy supporting multiple moqui instances on different hostnames. + +# Run with something like this for detached mode: +# $ docker compose -f moqui-postgres-compose.yml -p moqui up -d +# Or to copy runtime directories for mounted volumes, set default settings, etc use something like this: +# $ ./compose-run.sh moqui-postgres-compose.yml +# This sets the project/app name to 'moqui' and the network will be 'moqui_default', to be used by external moqui containers + +# Test locally by adding the virtual host to /etc/hosts or with something like: +# $ curl -H "Host: moqui.local" localhost/Login + +# To run an additional instance of moqui run something like this (but with +# many more arguments for volume mapping, db setup, etc): +# $ docker run -e VIRTUAL_HOST=moqui2.local --name moqui2_local --network moqui_default moqui + +# To import data from the docker host using port 5432 mapped for 127.0.0.1 only use something like this: +# $ psql -h 127.0.0.1 -p 5432 -U moqui -W moqui < pg-dump.sql + +version: "2" +services: + nginx-proxy: + # For documentation on SSL and other settings see: + # https://github.com/nginxproxy/nginx-proxy + image: nginxproxy/nginx-proxy + container_name: nginx-proxy + restart: always + ports: + - 80:80 + - 443:443 + labels: + com.github.jrcs.letsencrypt_nginx_proxy_companion.nginx_proxy: "true" + volumes: + - /var/run/docker.sock:/tmp/docker.sock:ro + - /etc/localtime:/etc/localtime:ro + # note: .crt, .key, and .dhparam.pem files start with the domain name in VIRTUAL_HOST (ie 'acetousk.com.*') or use CERT_NAME env var + - ./certs:/etc/nginx/certs + - ./nginx/conf.d:/etc/nginx/conf.d + - ./nginx/vhost.d:/etc/nginx/vhost.d + - ./nginx/html:/usr/share/nginx/html + environment: + # change this for the default host to use when accessing directly by IP, etc + - DEFAULT_HOST=moqui.local + # use SSL_POLICY to disable TLSv1.0, etc in nginx-proxy + - SSL_POLICY=AWS-TLS-1-1-2017-01 + networks: + - proxy-tier + + acme-companion: + image: nginxproxy/acme-companion + container_name: acme-companion + restart: always + volumes: + - /var/run/docker.sock:/var/run/docker.sock:ro + - /etc/localtime:/etc/localtime:ro + - ./certs:/etc/nginx/certs + - ./nginx/conf.d:/etc/nginx/conf.d + - ./nginx/vhost.d:/etc/nginx/vhost.d + - ./nginx/html:/usr/share/nginx/html + - ./acme.sh:/etc/acme.sh + networks: + - proxy-tier + environment: + # TODO: For production change this to your email + - DEFAULT_EMAIL=mail@yourdomain.tld + # TODO: For production change this to false + - LETSENCRYPT_TEST=true + depends_on: + - nginx-proxy + + moqui-server: + image: moqui + container_name: moqui-server + command: conf=conf/MoquiProductionConf.xml no-run-es + restart: always + links: + - moqui-database + - moqui-search + volumes: + - /etc/localtime:/etc/localtime:ro + - ./runtime/conf:/opt/moqui/runtime/conf + - ./runtime/lib:/opt/moqui/runtime/lib + - ./runtime/classes:/opt/moqui/runtime/classes + - ./runtime/ecomponent:/opt/moqui/runtime/ecomponent + - ./runtime/log:/opt/moqui/runtime/log + - ./runtime/txlog:/opt/moqui/runtime/txlog + - ./runtime/sessions:/opt/moqui/runtime/sessions + # this one isn't needed when not using H2/etc:- ./runtime/db:/opt/moqui/runtime/db + environment: + - "JAVA_TOOL_OPTIONS=-Xms1024m -Xmx1024m" + - instance_purpose=production + - entity_ds_db_conf=postgres + - entity_ds_host=moqui-database + - entity_ds_port=5432 + - entity_ds_database=moqui + - entity_ds_schema=public + - entity_ds_user=moqui + - entity_ds_password='MOQUI_CHANGE_ME!!!' + - entity_ds_crypt_pass='DEFAULT_CHANGE_ME!!!' + # configuration for ElasticFacade.ElasticClient, make sure the old moqui-elasticsearch component is NOT included in the Moqui build + - elasticsearch_url=https://moqui-search:9200 + # prefix for index names, use something distinct and not 'moqui_' or 'mantle_' which match the beginning of OOTB index names + - elasticsearch_index_prefix=default_ + - elasticsearch_user=admin + - elasticsearch_password=admin + # CHANGE ME - note that VIRTUAL_HOST is for nginx-proxy so it picks up this container as one it should reverse proxy + # this can be a comma separate list of hosts like 'example.com,www.example.com' + - VIRTUAL_HOST=moqui.local + - LETSENCRYPT_HOST=moqui.local + # moqui will accept traffic from other hosts but these are the values used for URL writing when specified: + # - webapp_http_host=moqui.local + - webapp_http_port=80 + - webapp_https_port=443 + - webapp_https_enabled=true + # nginx-proxy populates X-Real-IP with remote_addr by default, better option for outer proxy than X-Forwarded-For which defaults to proxy_add_x_forwarded_for + - webapp_client_ip_header=X-Real-IP + - default_locale=en_US + - default_time_zone=US/Pacific + networks: + - proxy-tier + - default + + moqui-database: + image: postgres:14.5 + container_name: moqui-database + restart: always + ports: + # change this as needed to bind to any address or even comment to not expose port outside containers + - 127.0.0.1:5432:5432 + volumes: + - /etc/localtime:/etc/localtime:ro + # edit these as needed to map configuration and data storage + - ./db/postgres/data:/var/lib/postgresql/data + environment: + - POSTGRES_DB=moqui + - POSTGRES_DB_SCHEMA=public + - POSTGRES_USER=moqui + - POSTGRES_PASSWORD='MOQUI_CHANGE_ME!!!' + # PGDATA, POSTGRES_INITDB_ARGS + networks: + default: + + moqui-search: + image: opensearchproject/opensearch:2.4.0 + container_name: moqui-search + restart: always + ports: + # change this as needed to bind to any address or even comment to not expose port outside containers + - 127.0.0.1:9200:9200 + - 127.0.0.1:9300:9300 + volumes: + - /etc/localtime:/etc/localtime:ro + # edit these as needed to map configuration and data storage + - ./opensearch/data/nodes:/usr/share/opensearch/data/nodes + # - ./opensearch/config/opensearch.yml:/usr/share/opensearch/config/opensearch.yml + # - ./opensearch/logs:/usr/share/opensearch/logs + environment: + - "OPENSEARCH_JAVA_OPTS=-Xms512m -Xmx512m" + - discovery.type=single-node + - network.host=_site_ + ulimits: + memlock: + soft: -1 + hard: -1 + nofile: + soft: 65536 + hard: 65536 + networks: + proxy-tier: + + opensearch-dashboards: + image: opensearchproject/opensearch-dashboards:2.4.0 + container_name: opensearch-dashboards + volumes: + - /etc/localtime:/etc/localtime:ro + links: + - moqui-search + ports: + - 127.0.0.1:5601:5601 + environment: + OPENSEARCH_HOSTS: '["https://moqui-search:9200"]' + networks: + default: + proxy-tier: + +networks: + proxy-tier: diff --git a/docker/simple/Dockerfile b/docker/simple/Dockerfile index 377f29858..54131450a 100644 --- a/docker/simple/Dockerfile +++ b/docker/simple/Dockerfile @@ -22,8 +22,8 @@ ARG search_name=opensearch RUN if [ -d runtime/opensearch/bin ]; then echo "Installing OpenSearch User"; \ search_name=opensearch; \ - groupadd -g 1000 opensearch && \ - useradd -u 1000 -g 1000 -G 0 -d /opt/moqui/runtime/opensearch opensearch && \ + groupadd -g 1000 opensearch 2>/dev/null || echo "group 1000 already exists" && \ + useradd -u 1000 -g 1000 -G 0 -d /opt/moqui/runtime/opensearch opensearch 2>/dev/null || echo "user 1000 already exists" && \ chmod 0775 /opt/moqui/runtime/opensearch && \ chown -R 1000:0 /opt/moqui/runtime/opensearch; \ elif [ -d runtime/elasticsearch/bin ]; then echo "Installing ElasticSearch User"; \ From 803f2aec3e8e491bf3d341da78a00442ab4978e6 Mon Sep 17 00:00:00 2001 From: acetousk Date: Wed, 31 Jul 2024 16:10:02 -0600 Subject: [PATCH 16/18] Add opensearch README file --- .gitignore | 3 ++- docker/opensearch/data/nodes/README | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) create mode 100644 docker/opensearch/data/nodes/README diff --git a/.gitignore b/.gitignore index c5a7289d2..19035c59b 100644 --- a/.gitignore +++ b/.gitignore @@ -14,7 +14,8 @@ build /docker/runtime /docker/db /docker/elasticsearch/data/nodes -/docker/opensearch/data/nodes +/docker/opensearch/data/nodes/* +!/docker/opensearch/data/nodes/README /docker/acme.sh /docker/nginx/conf.d /docker/nginx/vhost.d diff --git a/docker/opensearch/data/nodes/README b/docker/opensearch/data/nodes/README new file mode 100644 index 000000000..952316788 --- /dev/null +++ b/docker/opensearch/data/nodes/README @@ -0,0 +1 @@ +This directory must exist for mapping otherwise created as root in container and opensearch cannot access it. From d677e40c14e7a93b10eefed245e582ae3bad6e5d Mon Sep 17 00:00:00 2001 From: acetousk Date: Fri, 23 Aug 2024 16:33:49 -0600 Subject: [PATCH 17/18] Add flattenNestedMapWithKeys method for stripe conversion to application/x-www-form-urlencoded --- .../org/moqui/util/CollectionUtilities.java | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/framework/src/main/java/org/moqui/util/CollectionUtilities.java b/framework/src/main/java/org/moqui/util/CollectionUtilities.java index 3252956a8..ea7fe9459 100644 --- a/framework/src/main/java/org/moqui/util/CollectionUtilities.java +++ b/framework/src/main/java/org/moqui/util/CollectionUtilities.java @@ -460,6 +460,39 @@ public static Map flattenNestedMap(Map theMap) { return outMap; } + public static Map flattenNestedMapWithKeys(Map theMap) { + return flattenNestedMapWithKeys(theMap, ""); + } + + @SuppressWarnings("unchecked") + private static Map flattenNestedMapWithKeys(Map theMap, String parentKey) { + Map output = new LinkedHashMap<>(); + + if (theMap == null) return output; + + for (Map.Entry entry : theMap.entrySet()) { + String key = entry.getKey(); + Object value = entry.getValue(); + String newKey = parentKey.isEmpty() ? key : parentKey + "[" + key + "]"; + + if (value instanceof Map) { + output.putAll(flattenNestedMapWithKeys((Map) value, newKey)); + } else if (value instanceof Collection) { + int index = 0; + for (Object colValue : (Collection) value) { + if (colValue instanceof Map) { + output.putAll(flattenNestedMapWithKeys((Map) colValue, newKey + "[" + index + "]")); + } else { + output.put(newKey + "[" + index + "]", colValue.toString()); + } + index++; + } + } else { + output.put(newKey, value.toString()); + } + } + return output; + } @SuppressWarnings("unchecked") public static void mergeNestedMap(Map baseMap, Map overrideMap, boolean overrideEmpty) { if (baseMap == null || overrideMap == null) return; From 4fa9aa160044fe35dc54d0c3c12fcc6e7d70aea5 Mon Sep 17 00:00:00 2001 From: Hans Bakker Date: Sat, 26 Oct 2024 08:57:06 +0700 Subject: [PATCH 18/18] fix: Unable to release Session : Caused by: java.io.NotSerializableException: org.apache.groovy.json.internal.LazyMap --- framework/src/main/java/org/moqui/util/RestClient.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/framework/src/main/java/org/moqui/util/RestClient.java b/framework/src/main/java/org/moqui/util/RestClient.java index 46b389f7c..b2603bad9 100644 --- a/framework/src/main/java/org/moqui/util/RestClient.java +++ b/framework/src/main/java/org/moqui/util/RestClient.java @@ -14,7 +14,7 @@ package org.moqui.util; import groovy.json.JsonBuilder; -import groovy.json.JsonSlurper; +import groovy.json.JsonSlurperClassic; import org.eclipse.jetty.client.HttpClient; import org.eclipse.jetty.client.HttpClientTransport; import org.eclipse.jetty.client.HttpResponseException; @@ -442,7 +442,7 @@ public String text() { /** Parse the response as JSON and return an Object */ public Object jsonObject() { try { - return new JsonSlurper().parseText(text()); + return new JsonSlurperClassic().parseText(text()); } catch (Throwable t) { throw new BaseException("Error parsing JSON response from request to " + rci.uriString, t); }