Compare commits
29
Commits
d8ab42b09c
..
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
595e4eed27 | ||
|
|
a3fcab1c4b | ||
|
|
36c736f8d3 | ||
|
|
3eb4970407 | ||
|
|
ab74360571 | ||
|
|
fcda4c9e53 | ||
|
|
4a0aebce82 | ||
|
|
47f6eebd8b | ||
|
|
1dbb80e5a2 | ||
|
|
ed098513f5 | ||
|
|
1a5be5021b | ||
|
|
a9d4377599 | ||
|
|
af44532d26 | ||
|
|
fedb80f3c2 | ||
|
|
f0c84e809f | ||
|
|
4f3db5ae8b | ||
|
|
c9fc81cfca | ||
|
|
5e564fe9a7 | ||
|
|
9e0a33e782 | ||
|
|
514aaeb367 | ||
|
|
caabe7b923 | ||
|
|
f972436717 | ||
|
|
2ad194fab2 | ||
|
|
2c3cb48667 | ||
|
|
04088c20e5 | ||
|
|
34255c93c8 | ||
|
|
01deea380a | ||
|
|
d53ba82e3e | ||
|
|
ac5448385d |
@@ -9,6 +9,7 @@ build/
|
||||
.idea/jarRepositories.xml
|
||||
.idea/compiler.xml
|
||||
.idea/libraries/
|
||||
.idea/workspace.xml
|
||||
*.iws
|
||||
*.iml
|
||||
*.ipr
|
||||
@@ -40,3 +41,6 @@ bin/
|
||||
|
||||
### Mac OS ###
|
||||
.DS_Store
|
||||
|
||||
*.bat
|
||||
config/
|
||||
Generated
-389
@@ -1,389 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="AutoImportSettings">
|
||||
<option name="autoReloadType" value="SELECTIVE" />
|
||||
</component>
|
||||
<component name="ChangeListManager">
|
||||
<list default="true" id="ce59df2a-8d56-446a-867b-80e627daf479" name="Changes" comment="Add database configuration via command-line arguments A new configuration setup now allows specifying a path for database properties via command-line arguments during application startup. This update also changes the call signature for `DatabaseConnection.initialize()` method to accept a path argument. Similarly, methods in `PropertiesLoader`, `PropertiesWriter`, and `MailSettings` classes were also updated to use the specified path when working with properties files. The `TestForm` class's tests were updated accordingly to handle these changes.">
|
||||
<change afterPath="$PROJECT_DIR$/build.sh" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
|
||||
</list>
|
||||
<option name="SHOW_DIALOG" value="false" />
|
||||
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
|
||||
<option name="LAST_RESOLUTION" value="IGNORE" />
|
||||
</component>
|
||||
<component name="ExternalProjectsData">
|
||||
<projectState path="$PROJECT_DIR$">
|
||||
<ProjectState />
|
||||
</projectState>
|
||||
</component>
|
||||
<component name="ExternalProjectsManager">
|
||||
<system id="GRADLE">
|
||||
<state>
|
||||
<task path="$PROJECT_DIR$">
|
||||
<activation />
|
||||
</task>
|
||||
<projects_view>
|
||||
<tree_state>
|
||||
<expand>
|
||||
<path>
|
||||
<item name="" type="6a2764b6:ExternalProjectsStructure$RootNode" />
|
||||
<item name="forms" type="f1a62948:ProjectNode" />
|
||||
</path>
|
||||
<path>
|
||||
<item name="" type="6a2764b6:ExternalProjectsStructure$RootNode" />
|
||||
<item name="forms" type="f1a62948:ProjectNode" />
|
||||
<item name="Tasks" type="e4a08cd1:TasksNode" />
|
||||
</path>
|
||||
</expand>
|
||||
<select />
|
||||
</tree_state>
|
||||
</projects_view>
|
||||
</state>
|
||||
</system>
|
||||
</component>
|
||||
<component name="FileTemplateManagerImpl">
|
||||
<option name="RECENT_TEMPLATES">
|
||||
<list>
|
||||
<option value="Jsp File" />
|
||||
<option value="Class" />
|
||||
<option value="package-info" />
|
||||
<option value="package.json" />
|
||||
</list>
|
||||
</option>
|
||||
</component>
|
||||
<component name="Git.Settings">
|
||||
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
|
||||
</component>
|
||||
<component name="GitHubPullRequestSearchHistory">{
|
||||
"lastFilter": {
|
||||
"state": "OPEN",
|
||||
"assignee": "Teriuihi"
|
||||
}
|
||||
}</component>
|
||||
<component name="GitToolBoxStore">
|
||||
<option name="projectConfigVersion" value="5" />
|
||||
</component>
|
||||
<component name="GithubPullRequestsUISettings">{
|
||||
"selectedUrlAndAccountId": {
|
||||
"url": "[email protected]:Altitude-Devs/SiteBackend.git",
|
||||
"accountId": "ad9eb076-a98f-40c2-b7e5-9fc28b23038f"
|
||||
}
|
||||
}</component>
|
||||
<component name="KubernetesApiProvider">{
|
||||
"isMigrated": true
|
||||
}</component>
|
||||
<component name="LogFilters">
|
||||
<option name="FILTER_ERRORS" value="false" />
|
||||
<option name="FILTER_WARNINGS" value="false" />
|
||||
<option name="FILTER_INFO" value="true" />
|
||||
<option name="FILTER_DEBUG" value="true" />
|
||||
<option name="CUSTOM_FILTER" />
|
||||
</component>
|
||||
<component name="MarkdownSettingsMigration">
|
||||
<option name="stateVersion" value="1" />
|
||||
</component>
|
||||
<component name="ProblemsViewState">
|
||||
<option name="selectedTabId" value="ProjectErrors" />
|
||||
</component>
|
||||
<component name="ProjectColorInfo">{
|
||||
"associatedIndex": 7
|
||||
}</component>
|
||||
<component name="ProjectId" id="2aJVOGgvEjzOGrKFJ1xKKH5Oshq" />
|
||||
<component name="ProjectLevelVcsManager" settingsEditedManually="true" />
|
||||
<component name="ProjectViewState">
|
||||
<option name="hideEmptyMiddlePackages" value="true" />
|
||||
<option name="showLibraryContents" value="true" />
|
||||
</component>
|
||||
<component name="PropertiesComponent">{
|
||||
"keyToString": {
|
||||
"Gradle.Build forms.executor": "Run",
|
||||
"Gradle.TestForm.executor": "Run",
|
||||
"Gradle.TestForm.testFunctions.executor": "Debug",
|
||||
"Gradle.TestForm.testRetrieveForm.executor": "Run",
|
||||
"RequestMappingsPanelOrder0": "0",
|
||||
"RequestMappingsPanelOrder1": "1",
|
||||
"RequestMappingsPanelWidth0": "75",
|
||||
"RequestMappingsPanelWidth1": "75",
|
||||
"RunOnceActivity.OpenProjectViewOnStart": "true",
|
||||
"RunOnceActivity.ShowReadmeOnStart": "true",
|
||||
"SHARE_PROJECT_CONFIGURATION_FILES": "true",
|
||||
"Spring Boot.Main.executor": "Run",
|
||||
"Tomcat Server.Tomcat 10.1.17.executor": "Run",
|
||||
"git-widget-placeholder": "master",
|
||||
"ignore.virus.scanning.warn.message": "true",
|
||||
"kotlin-language-version-configured": "true",
|
||||
"last_opened_file_path": "P:/Code/Plugins/forms",
|
||||
"node.js.detected.package.eslint": "true",
|
||||
"node.js.detected.package.tslint": "true",
|
||||
"node.js.selected.package.eslint": "(autodetect)",
|
||||
"node.js.selected.package.tslint": "(autodetect)",
|
||||
"nodejs_package_manager_path": "npm",
|
||||
"project.structure.last.edited": "Project",
|
||||
"project.structure.proportion": "0.0",
|
||||
"project.structure.side.proportion": "0.0",
|
||||
"run.code.analysis.last.selected.profile": "pProject Default",
|
||||
"settings.editor.selected.configurable": "preferences.lookFeel",
|
||||
"vue.rearranger.settings.migration": "true"
|
||||
},
|
||||
"keyToStringList": {
|
||||
"DatabaseDriversLRU": [
|
||||
"mysql",
|
||||
"mariadb"
|
||||
]
|
||||
}
|
||||
}</component>
|
||||
<component name="RunManager" selected="Gradle.TestForm">
|
||||
<configuration name="TestForm" type="GradleRunConfiguration" factoryName="Gradle" temporary="true">
|
||||
<ExternalSystemSettings>
|
||||
<option name="executionName" />
|
||||
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||
<option name="externalSystemIdString" value="GRADLE" />
|
||||
<option name="scriptParameters" value="" />
|
||||
<option name="taskDescriptions">
|
||||
<list />
|
||||
</option>
|
||||
<option name="taskNames">
|
||||
<list>
|
||||
<option value=":test" />
|
||||
<option value="--tests" />
|
||||
<option value=""TestForm"" />
|
||||
</list>
|
||||
</option>
|
||||
<option name="vmOptions" />
|
||||
</ExternalSystemSettings>
|
||||
<ExternalSystemDebugServerProcess>false</ExternalSystemDebugServerProcess>
|
||||
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
|
||||
<DebugAllEnabled>false</DebugAllEnabled>
|
||||
<RunAsTest>true</RunAsTest>
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
<configuration name="TestForm.testFunctions" type="GradleRunConfiguration" factoryName="Gradle" temporary="true">
|
||||
<ExternalSystemSettings>
|
||||
<option name="executionName" />
|
||||
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||
<option name="externalSystemIdString" value="GRADLE" />
|
||||
<option name="scriptParameters" value="" />
|
||||
<option name="taskDescriptions">
|
||||
<list />
|
||||
</option>
|
||||
<option name="taskNames">
|
||||
<list>
|
||||
<option value=":test" />
|
||||
<option value="--tests" />
|
||||
<option value=""TestForm.testFunctions"" />
|
||||
</list>
|
||||
</option>
|
||||
<option name="vmOptions" />
|
||||
</ExternalSystemSettings>
|
||||
<ExternalSystemDebugServerProcess>false</ExternalSystemDebugServerProcess>
|
||||
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
|
||||
<DebugAllEnabled>false</DebugAllEnabled>
|
||||
<RunAsTest>true</RunAsTest>
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
<configuration name="TestForm.testRetrieveForm" type="GradleRunConfiguration" factoryName="Gradle" temporary="true">
|
||||
<ExternalSystemSettings>
|
||||
<option name="executionName" />
|
||||
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||
<option name="externalSystemIdString" value="GRADLE" />
|
||||
<option name="scriptParameters" value="" />
|
||||
<option name="taskDescriptions">
|
||||
<list />
|
||||
</option>
|
||||
<option name="taskNames">
|
||||
<list>
|
||||
<option value=":test" />
|
||||
<option value="--tests" />
|
||||
<option value=""TestForm.testRetrieveForm"" />
|
||||
</list>
|
||||
</option>
|
||||
<option name="vmOptions" />
|
||||
</ExternalSystemSettings>
|
||||
<ExternalSystemDebugServerProcess>false</ExternalSystemDebugServerProcess>
|
||||
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
|
||||
<DebugAllEnabled>false</DebugAllEnabled>
|
||||
<RunAsTest>true</RunAsTest>
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
<configuration name="Main" type="SpringBootApplicationConfigurationType" factoryName="Spring Boot" nameIsGenerated="true">
|
||||
<option name="ACTIVE_PROFILES" value="dev" />
|
||||
<module name="forms.main" />
|
||||
<option name="SPRING_BOOT_MAIN_CLASS" value="com.alttd.forms.Main" />
|
||||
<method v="2">
|
||||
<option name="Make" enabled="true" />
|
||||
</method>
|
||||
</configuration>
|
||||
<configuration name="Tomcat 10.1.17" type="#com.intellij.j2ee.web.tomcat.TomcatRunConfigurationFactory" factoryName="Local" APPLICATION_SERVER_NAME="Tomcat 10.1.17" ALTERNATIVE_JRE_ENABLED="false">
|
||||
<deployment>
|
||||
<artifact name="Gradle : com.alttd : forms-1.0-SNAPSHOT.war">
|
||||
<settings>
|
||||
<option name="CONTEXT_PATH" value="/Gradle___com_alttd___forms_1_0_SNAPSHOT_war" />
|
||||
</settings>
|
||||
</artifact>
|
||||
</deployment>
|
||||
<server-settings>
|
||||
<option name="BASE_DIRECTORY_NAME" value="f710d80b-33cf-48d5-9d94-7e9dacc0f171" />
|
||||
</server-settings>
|
||||
<predefined_log_file enabled="true" id="Tomcat" />
|
||||
<predefined_log_file enabled="true" id="Tomcat Catalina" />
|
||||
<predefined_log_file id="Tomcat Manager" />
|
||||
<predefined_log_file id="Tomcat Host Manager" />
|
||||
<predefined_log_file id="Tomcat Localhost Access" />
|
||||
<ConfigurationWrapper VM_VAR="JAVA_OPTS" RunnerId="Run">
|
||||
<option name="USE_ENV_VARIABLES" value="true" />
|
||||
<STARTUP>
|
||||
<option name="USE_DEFAULT" value="true" />
|
||||
<option name="SCRIPT" value="" />
|
||||
<option name="VM_PARAMETERS" value="" />
|
||||
<option name="PROGRAM_PARAMETERS" value="" />
|
||||
</STARTUP>
|
||||
<SHUTDOWN>
|
||||
<option name="USE_DEFAULT" value="true" />
|
||||
<option name="SCRIPT" value="" />
|
||||
<option name="VM_PARAMETERS" value="" />
|
||||
<option name="PROGRAM_PARAMETERS" value="" />
|
||||
</SHUTDOWN>
|
||||
</ConfigurationWrapper>
|
||||
<method v="2">
|
||||
<option name="Make" enabled="true" />
|
||||
<option name="BuildArtifacts" enabled="true">
|
||||
<artifact name="Gradle : com.alttd : forms-1.0-SNAPSHOT.war" />
|
||||
</option>
|
||||
</method>
|
||||
</configuration>
|
||||
<list>
|
||||
<item itemvalue="Gradle.TestForm" />
|
||||
<item itemvalue="Gradle.TestForm.testFunctions" />
|
||||
<item itemvalue="Gradle.TestForm.testRetrieveForm" />
|
||||
<item itemvalue="Spring Boot.Main" />
|
||||
<item itemvalue="Tomcat Server.Tomcat 10.1.17" />
|
||||
</list>
|
||||
<recent_temporary>
|
||||
<list>
|
||||
<item itemvalue="Gradle.TestForm" />
|
||||
<item itemvalue="Gradle.TestForm.testRetrieveForm" />
|
||||
<item itemvalue="Gradle.TestForm.testFunctions" />
|
||||
</list>
|
||||
</recent_temporary>
|
||||
</component>
|
||||
<component name="SharedIndexes">
|
||||
<attachedChunks>
|
||||
<set>
|
||||
<option value="bundled-jdk-9f38398b9061-39b83d9b5494-intellij.indexing.shared.core-IU-241.18034.62" />
|
||||
<option value="bundled-js-predefined-1d06a55b98c1-0b3e54e931b4-JavaScript-IU-241.18034.62" />
|
||||
</set>
|
||||
</attachedChunks>
|
||||
</component>
|
||||
<component name="SpellCheckerSettings" RuntimeDictionaries="0" Folders="0" CustomDictionaries="0" DefaultDictionary="application-level" UseSingleDictionary="true" transferred="true" />
|
||||
<component name="TaskManager">
|
||||
<task active="true" id="Default" summary="Default task">
|
||||
<changelist id="ce59df2a-8d56-446a-867b-80e627daf479" name="Changes" comment="" />
|
||||
<created>1704036635730</created>
|
||||
<option name="number" value="Default" />
|
||||
<option name="presentableId" value="Default" />
|
||||
<updated>1704036635730</updated>
|
||||
<workItem from="1704036636799" duration="105000" />
|
||||
<workItem from="1704549979998" duration="2844000" />
|
||||
<workItem from="1704629811001" duration="6145000" />
|
||||
<workItem from="1704639914630" duration="2938000" />
|
||||
<workItem from="1704742191416" duration="685000" />
|
||||
<workItem from="1705152717665" duration="6704000" />
|
||||
<workItem from="1705220399998" duration="6067000" />
|
||||
<workItem from="1705262555334" duration="590000" />
|
||||
<workItem from="1707329373408" duration="2000" />
|
||||
<workItem from="1707329426177" duration="24000" />
|
||||
<workItem from="1707417916223" duration="855000" />
|
||||
<workItem from="1707418804088" duration="1078000" />
|
||||
<workItem from="1707560440225" duration="1006000" />
|
||||
<workItem from="1707643318784" duration="1003000" />
|
||||
<workItem from="1707646300783" duration="1325000" />
|
||||
<workItem from="1707656217023" duration="2305000" />
|
||||
<workItem from="1708153393312" duration="669000" />
|
||||
<workItem from="1708154142819" duration="609000" />
|
||||
<workItem from="1714315469459" duration="9330000" />
|
||||
<workItem from="1714839358412" duration="595000" />
|
||||
<workItem from="1714855192234" duration="585000" />
|
||||
<workItem from="1714898672231" duration="2676000" />
|
||||
<workItem from="1722697616738" duration="2649000" />
|
||||
<workItem from="1722718451005" duration="2120000" />
|
||||
<workItem from="1722786481232" duration="617000" />
|
||||
<workItem from="1722801254695" duration="601000" />
|
||||
</task>
|
||||
<task id="LOCAL-00001" summary="Initial commit for site for forms">
|
||||
<option name="closed" value="true" />
|
||||
<created>1705159506039</created>
|
||||
<option name="number" value="00001" />
|
||||
<option name="presentableId" value="LOCAL-00001" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1705159506039</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00002" summary="Refactor code to use Form objects instead of JSON strings Several parts of the code have been altered to use Form objects instead of JSON strings. Changes include updating the FormQueryResult record type to hold an Optional<Form> instead of an Optional<String>, altering methods in the StoreFormQuery class to insert Form data into the database and replacing JSON handling methods in the FormQuery class with Form object oriented methods. A 'form_class' field has also been added to the 'form' table in the database to aid form identification and reconstruction from stored data.">
|
||||
<option name="closed" value="true" />
|
||||
<created>1705226962796</created>
|
||||
<option name="number" value="00002" />
|
||||
<option name="presentableId" value="LOCAL-00002" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1705226962796</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00003" summary="Add TestForm class and set up related unit tests The changes add a new TestForm class to implement unit tests for form handling in the application. The included tests verify the storing, retrieving, and verification of forms from a database, as well as form object creation and confirmation of form data. This is a step towards improving the code's reliability and making it easier to catch potential bugs or issues.">
|
||||
<option name="closed" value="true" />
|
||||
<created>1705226977031</created>
|
||||
<option name="number" value="00003" />
|
||||
<option name="presentableId" value="LOCAL-00003" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1705226977031</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00004" summary="Mark fields as final in VerificationData and ContactFormData The 'code', 'eMail' fields in the VerificationData class and 'username', 'email', 'question' fields in the ContactFormData class are now marked as 'final'. At the same time, some unused imports from ContactController, ContactFormData, FormQuery, and VerifyController have been removed for code cleanliness.">
|
||||
<option name="closed" value="true" />
|
||||
<created>1705227608210</created>
|
||||
<option name="number" value="00004" />
|
||||
<option name="presentableId" value="LOCAL-00004" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1705227608210</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00005" summary="Update Form HTML output and disable validation auto-configuration The form-to-HTML output process has been switched from a `<div>` approach to using a `StringBuilder` with a table structure in `ContactFormData.java`. Also, the spring validation auto-configuration has been disabled by adding `exclude = ValidationAutoConfiguration.class` in the `@SpringBootApplication` annotation of `Main.java`. Some changes in `.idea/workspace.xml` and `VerifyController.java` were made as well.">
|
||||
<option name="closed" value="true" />
|
||||
<created>1714319923708</created>
|
||||
<option name="number" value="00005" />
|
||||
<option name="presentableId" value="LOCAL-00005" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1714319923708</updated>
|
||||
</task>
|
||||
<task id="LOCAL-00006" summary="Add database configuration via command-line arguments A new configuration setup now allows specifying a path for database properties via command-line arguments during application startup. This update also changes the call signature for `DatabaseConnection.initialize()` method to accept a path argument. Similarly, methods in `PropertiesLoader`, `PropertiesWriter`, and `MailSettings` classes were also updated to use the specified path when working with properties files. The `TestForm` class's tests were updated accordingly to handle these changes.">
|
||||
<option name="closed" value="true" />
|
||||
<created>1714332311321</created>
|
||||
<option name="number" value="00006" />
|
||||
<option name="presentableId" value="LOCAL-00006" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1714332311321</updated>
|
||||
</task>
|
||||
<option name="localTasksCounter" value="7" />
|
||||
<servers />
|
||||
</component>
|
||||
<component name="TypeScriptGeneratedFilesManager">
|
||||
<option name="version" value="3" />
|
||||
</component>
|
||||
<component name="Vcs.Log.Tabs.Properties">
|
||||
<option name="TAB_STATES">
|
||||
<map>
|
||||
<entry key="MAIN">
|
||||
<value>
|
||||
<State />
|
||||
</value>
|
||||
</entry>
|
||||
</map>
|
||||
</option>
|
||||
</component>
|
||||
<component name="VcsManagerConfiguration">
|
||||
<MESSAGE value="Initial commit for site for forms" />
|
||||
<MESSAGE value="Refactor code to use Form objects instead of JSON strings Several parts of the code have been altered to use Form objects instead of JSON strings. Changes include updating the FormQueryResult record type to hold an Optional<Form> instead of an Optional<String>, altering methods in the StoreFormQuery class to insert Form data into the database and replacing JSON handling methods in the FormQuery class with Form object oriented methods. A 'form_class' field has also been added to the 'form' table in the database to aid form identification and reconstruction from stored data." />
|
||||
<MESSAGE value="Add TestForm class and set up related unit tests The changes add a new TestForm class to implement unit tests for form handling in the application. The included tests verify the storing, retrieving, and verification of forms from a database, as well as form object creation and confirmation of form data. This is a step towards improving the code's reliability and making it easier to catch potential bugs or issues." />
|
||||
<MESSAGE value="Remove unnecessary Gson imports in ContactFormData The Gson and GsonBuilder imports in the ContactFormData class were unused and have been removed. This cleanup simplifies the code and reduces unnecessary dependencies. " />
|
||||
<MESSAGE value="Mark fields as final in VerificationData and ContactFormData The 'code', 'eMail' fields in the VerificationData class and 'username', 'email', 'question' fields in the ContactFormData class are now marked as 'final'. At the same time, some unused imports from ContactController, ContactFormData, FormQuery, and VerifyController have been removed for code cleanliness." />
|
||||
<MESSAGE value="Update Form HTML output and disable validation auto-configuration The form-to-HTML output process has been switched from a `<div>` approach to using a `StringBuilder` with a table structure in `ContactFormData.java`. Also, the spring validation auto-configuration has been disabled by adding `exclude = ValidationAutoConfiguration.class` in the `@SpringBootApplication` annotation of `Main.java`. Some changes in `.idea/workspace.xml` and `VerifyController.java` were made as well." />
|
||||
<MESSAGE value="Add database configuration via command-line arguments A new configuration setup now allows specifying a path for database properties via command-line arguments during application startup. This update also changes the call signature for `DatabaseConnection.initialize()` method to accept a path argument. Similarly, methods in `PropertiesLoader`, `PropertiesWriter`, and `MailSettings` classes were also updated to use the specified path when working with properties files. The `TestForm` class's tests were updated accordingly to handle these changes." />
|
||||
<option name="LAST_COMMIT_MESSAGE" value="Add database configuration via command-line arguments A new configuration setup now allows specifying a path for database properties via command-line arguments during application startup. This update also changes the call signature for `DatabaseConnection.initialize()` method to accept a path argument. Similarly, methods in `PropertiesLoader`, `PropertiesWriter`, and `MailSettings` classes were also updated to use the specified path when working with properties files. The `TestForm` class's tests were updated accordingly to handle these changes." />
|
||||
</component>
|
||||
</project>
|
||||
Vendored
+6
@@ -12,6 +12,12 @@ pipeline {
|
||||
}
|
||||
}
|
||||
stage('discord') {
|
||||
when {
|
||||
anyOf {
|
||||
branch 'main'
|
||||
branch 'master'
|
||||
}
|
||||
}
|
||||
steps {
|
||||
discordSend description: "Build: ${BUILD_NUMBER}", showChangeset: true, result: currentBuild.currentResult, title: currentBuild.fullProjectName, webhookURL: env.discordwebhook
|
||||
}
|
||||
|
||||
+1
-1
@@ -27,8 +27,8 @@ dependencies {
|
||||
implementation 'org.springframework.boot:spring-boot-starter-validation'
|
||||
implementation group: 'javax.mail', name: 'mail', version: '1.4'
|
||||
implementation 'org.springframework.boot:spring-boot-starter-logging'
|
||||
implementation 'com.google.code.gson:gson:2.8.9'
|
||||
implementation 'org.mariadb.jdbc:mariadb-java-client:2.1.2'
|
||||
implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.12.3'
|
||||
}
|
||||
|
||||
test {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.alttd.forms;
|
||||
|
||||
import com.alttd.forms.database.DatabaseConnection;
|
||||
import com.alttd.forms.mail.rate_limitter.FormRateLimit;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration;
|
||||
@@ -16,6 +17,7 @@ public class Main {
|
||||
path = args[0];
|
||||
SpringApplication.run(Main.class, args);
|
||||
DatabaseConnection.initialize(args[0]);
|
||||
FormRateLimit.initialize(args[0]);
|
||||
}
|
||||
|
||||
public static String getConfigPath() {
|
||||
|
||||
@@ -15,8 +15,7 @@ public class WebConfig implements WebMvcConfigurer { //TODO this can be done wit
|
||||
@Override
|
||||
public void addCorsMappings(CorsRegistry registry) {
|
||||
registry.addMapping("/api/**")
|
||||
.allowedOrigins("http://localhost:3000")
|
||||
.allowedOrigins("https://forms.alttd.com")
|
||||
.allowedOrigins("http://localhost:3000", "https://forms.alttd.com")
|
||||
.allowedMethods("GET", "POST", "PUT", "DELETE")
|
||||
.allowedHeaders("*")
|
||||
.allowCredentials(true);
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
package com.alttd.forms.contact;
|
||||
import com.alttd.forms.mail.verification.VerificationResult;
|
||||
import com.alttd.forms.mail.verification.Verify;
|
||||
import jakarta.validation.Valid;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/contact")
|
||||
public class ContactController {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(ContactController.class);
|
||||
|
||||
@PostMapping("/submitContactForm")
|
||||
public CompletableFuture<ResponseEntity<String>> submitForm(@Valid @RequestBody ContactFormData formData) {
|
||||
logger.debug("submitForm");
|
||||
logger.trace(formData.toString());
|
||||
|
||||
CompletableFuture<Integer> storeFormForVerificationCode = new StoreFormQuery().storeFormForVerificationCode(formData.email, formData);
|
||||
return storeFormForVerificationCode.thenCompose(code -> Verify.verifyEmail(formData.email, code).thenApply(verificationResult -> {
|
||||
if (verificationResult == VerificationResult.VERIFICATION_SENT) {
|
||||
//TODO if this is ok tell the user they have x min to verify if they fail to do so they have to remake the form
|
||||
logger.trace("Contact form stored and requested verification from user");
|
||||
return ResponseEntity.ok("User Data received and email verification sent.");
|
||||
} else {
|
||||
logger.trace("Failed to send verification email");
|
||||
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
|
||||
.body("Failed to send verification email. Reason: " + verificationResult.name());
|
||||
}
|
||||
})).exceptionally(throwable -> {
|
||||
logger.error("Failed to store form", throwable);
|
||||
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Failed to store your form");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
package com.alttd.forms.controlers;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.validation.FieldError;
|
||||
import org.springframework.web.bind.MethodArgumentNotValidException;
|
||||
import org.springframework.web.bind.annotation.ControllerAdvice;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@ControllerAdvice
|
||||
public class ControllerExceptionHandler {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(ControllerExceptionHandler.class);
|
||||
|
||||
public ControllerExceptionHandler() {
|
||||
logger.info("ControllerExceptionHandler initialized"); // Ensure this gets logged
|
||||
}
|
||||
|
||||
@ExceptionHandler(MethodArgumentNotValidException.class)
|
||||
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||
public ResponseEntity<Map<String, String>> handleValidationExceptions(MethodArgumentNotValidException ex) {
|
||||
logger.debug("invalid request", ex);
|
||||
Map<String, String> errors = new HashMap<>();
|
||||
ex.getBindingResult().getAllErrors().forEach((error) -> {
|
||||
String fieldName = ((FieldError) error).getField();
|
||||
String errorMessage = error.getDefaultMessage();
|
||||
errors.put(fieldName, errorMessage);
|
||||
});
|
||||
if (logger.isDebugEnabled()) {
|
||||
try {
|
||||
String errorsAsString = new ObjectMapper().writeValueAsString(errors);
|
||||
logger.debug("Invalid parameters\n{}", errorsAsString);
|
||||
} catch (JsonProcessingException e) {
|
||||
logger.warn("Failed to parse errors to json", e);
|
||||
}
|
||||
}
|
||||
return ResponseEntity.badRequest().body(errors);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package com.alttd.forms.controlers;
|
||||
|
||||
import com.alttd.forms.form.Form;
|
||||
import com.alttd.forms.form.StoreFormQuery;
|
||||
import com.alttd.forms.mail.verification.VerificationResult;
|
||||
import com.alttd.forms.mail.verification.Verify;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
public class FormRequestHandler {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(FormRequestHandler.class);
|
||||
|
||||
public static CompletableFuture<ResponseEntity<String>> handleRequestWithVerifyMail(Form form, String ip) {
|
||||
CompletableFuture<Integer> storeFormForVerificationCode = new StoreFormQuery().storeFormForVerificationCode(form.getSender(), form, ip);
|
||||
return storeFormForVerificationCode.thenCompose(code -> Verify.verifyEmail(ip, form.getSender(), code, form).thenApply(verificationResult -> {
|
||||
if (verificationResult == VerificationResult.VERIFICATION_SENT) {
|
||||
//TODO if this is ok tell the user they have x min to verify if they fail to do so they have to remake the form
|
||||
logger.trace("Staff application form stored and requested verification from user");
|
||||
return ResponseEntity.ok("User Data received and email verification sent.");
|
||||
} else if (verificationResult == VerificationResult.RATE_LIMIT_EXCEEDED) {
|
||||
logger.trace("User hit the rate limit email: {} ip: {}", form.getSender(), ip);
|
||||
return ResponseEntity.status(HttpStatus.TOO_MANY_REQUESTS).body("You have sent too many forms today, " +
|
||||
"please wait up to an hour before trying again. " +
|
||||
"You can resubmit your form by hitting submit again later. " +
|
||||
"Continued use of the form will extent the time you have to wait.");
|
||||
} else {
|
||||
logger.trace("Failed to send verification email {}", verificationResult.name());
|
||||
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
|
||||
.body("Failed to send verification email. Reason: " + verificationResult.name());
|
||||
}
|
||||
})).exceptionally(throwable -> {
|
||||
logger.error("Failed to store form", throwable);
|
||||
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Failed to store your form");
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.alttd.forms.controlers.apply;
|
||||
|
||||
import com.alttd.forms.controlers.FormRequestHandler;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.validation.Valid;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/apply")
|
||||
public class StaffAppController {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(StaffAppController.class);
|
||||
|
||||
@PostMapping("/staffApplication")
|
||||
public CompletableFuture<ResponseEntity<String>> submitForm(@Valid @RequestBody StaffAppFormData formData, HttpServletRequest request) {
|
||||
logger.debug("submitForm");
|
||||
logger.trace(formData.toString());
|
||||
|
||||
return FormRequestHandler.handleRequestWithVerifyMail(formData, request.getRemoteAddr());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
package com.alttd.forms.controlers.apply;
|
||||
|
||||
import com.alttd.forms.form.Form;
|
||||
import com.alttd.forms.mail.mail_forms.Mailable;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import jakarta.validation.constraints.*;
|
||||
import org.hibernate.validator.constraints.Length;
|
||||
import org.hibernate.validator.constraints.Range;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.Optional;
|
||||
|
||||
public class StaffAppFormData extends Form implements Mailable {
|
||||
|
||||
public StaffAppFormData() {}
|
||||
|
||||
public StaffAppFormData(String username, String email, String discord, String pc_requirements, int age, String pronoun, LocalDate join_date, int avg_time, String available_days, String available_time, String staff_experience, String plugin_experience, String why_staff, String expectations_mod, String other) {
|
||||
this.username = username;
|
||||
this.email = email;
|
||||
this.discord = discord;
|
||||
this.pc_requirements = pc_requirements;
|
||||
this.age = age;
|
||||
this.pronoun = pronoun;
|
||||
this.join_date = join_date;
|
||||
this.avg_time = avg_time;
|
||||
this.available_days = available_days;
|
||||
this.available_time = available_time;
|
||||
this.staff_experience = staff_experience;
|
||||
this.plugin_experience = plugin_experience;
|
||||
this.why_staff = why_staff;
|
||||
this.expectations_mod = expectations_mod;
|
||||
this.other = other;
|
||||
}
|
||||
|
||||
@NotEmpty(message = "Username is required")
|
||||
@Length(min = 3, max = 16, message = "Username should be between 3 and 16 characters")
|
||||
@Pattern(regexp = "^[a-zA-Z0-9_]*$", message = "Username should only include alphanumeric characters and underscore")
|
||||
public String username;
|
||||
|
||||
@NotEmpty(message = "E-mail address is required")
|
||||
@Email(message = "Invalid email")
|
||||
@Length(min = 3, max = 254, message = "Email should be between 3 and 254 characters")
|
||||
public String email;
|
||||
|
||||
@NotEmpty(message = "Discord name is required")
|
||||
@Length(min = 2, max = 32, message = "Discord name should be between 2 and 32 characters")
|
||||
@Pattern(regexp = "^([a-z0-9._]{2,32})$", message = "Please enter a valid Discord name")
|
||||
public String discord;
|
||||
|
||||
@NotEmpty(message = "An answer is required")
|
||||
@Length(min = 2, max = 3, message = "Please answer yes or no")
|
||||
@Pattern(regexp = "(?i)^(yes|no)$", message = "Yes or no")
|
||||
public String pc_requirements;
|
||||
|
||||
@Min(value = 0, message = "Please enter a valid age")
|
||||
@Max(value = 999, message = "We do not accept players older than 999 years old sorry!")
|
||||
public int age;
|
||||
|
||||
@Length(max = 16, message = "Pronouns can't be longer than 16 characters")
|
||||
public String pronoun;
|
||||
|
||||
@NotNull(message = "Your join date is required, if you're not sure enter an estimated date")
|
||||
public LocalDate join_date;
|
||||
|
||||
@Range(min = 0, max = 168, message = "The only valid values are 0-168")
|
||||
public int avg_time;
|
||||
|
||||
@NotEmpty(message = "Available days are required")
|
||||
@Length(min = 6, max = 128, message = "Available days should be between 6 and 128 characters")
|
||||
public String available_days;
|
||||
|
||||
@NotEmpty(message = "Available time is required")
|
||||
@Length(min = 3, max = 256, message = "Available time should be between 3 and 256 characters")
|
||||
public String available_time;
|
||||
|
||||
@NotEmpty(message = "Staff experience is required")
|
||||
@Length(min = 2, max = 2000, message = "Experience should be between 2 and 2000 characters")
|
||||
public String staff_experience;
|
||||
|
||||
@NotEmpty(message = "Plugin experience is required")
|
||||
@Length(min = 2, max = 2000, message = "Experience should be between 2 and 2000 characters")
|
||||
public String plugin_experience;
|
||||
|
||||
@NotEmpty(message = "Reason for wanting to be a moderator is required")
|
||||
@Length(min = 2, max =2000, message = "Reason should be between 2 and 2000 characters")
|
||||
public String why_staff;
|
||||
|
||||
@NotEmpty(message = "Expectations of a moderator is required")
|
||||
@Length(min = 2, max = 2000, message = "Expectation should be between 2 and 2000 characters")
|
||||
public String expectations_mod;
|
||||
|
||||
@Length(max = 2000, message = "Text can't be longer than 2000 characters")
|
||||
public String other;
|
||||
|
||||
@JsonIgnore
|
||||
@Override
|
||||
public String toString() {
|
||||
return "StaffAppFormData{" +
|
||||
"username='" + username + '\'' +
|
||||
", email='" + email + '\'' +
|
||||
", discord='" + discord + '\'' +
|
||||
", pc_requirements='" + pc_requirements + '\'' +
|
||||
", age=" + age +
|
||||
", pronoun='" + pronoun + '\'' +
|
||||
", join_date=" + join_date +
|
||||
", avg_time=" + avg_time +
|
||||
", available_days='" + available_days + '\'' +
|
||||
", available_time='" + available_time + '\'' +
|
||||
", staff_experience='" + staff_experience + '\'' +
|
||||
", plugin_experience='" + plugin_experience + '\'' +
|
||||
", why_staff='" + why_staff + '\'' +
|
||||
", expectations_mod='" + expectations_mod + '\'' +
|
||||
", other='" + other + '\'' +
|
||||
'}';
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
@Override
|
||||
public Optional<String> getDiscordBotUrl() {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
@Override
|
||||
public String getReceiver() {
|
||||
return "[email protected]";
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
@Override
|
||||
public String getSubject() {
|
||||
return "Staff Application: " + username;
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
@Override
|
||||
public String getSender() {
|
||||
return email;
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
@Override
|
||||
public String toHtml() {
|
||||
String[] fields = {"Username", "Email", "Discord", "PC requirements", "Age", "Pronoun", "Join date", "Avg time", "Available days", "Available time", "Staff experience", "Plugin experience", "Why staff", "Expectations mod", "Other"};
|
||||
String[] values = {username, email, discord, pc_requirements, String.valueOf(age), pronoun, join_date.toString(), String.valueOf(avg_time), available_days, available_time, staff_experience, plugin_experience, why_staff, expectations_mod, other};
|
||||
|
||||
return toHtml(fields, values);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.alttd.forms.controlers.contact;
|
||||
import com.alttd.forms.controlers.FormRequestHandler;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.validation.Valid;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/contact")
|
||||
public class ContactController {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(ContactController.class);
|
||||
|
||||
@PostMapping("/submitContactForm")
|
||||
public CompletableFuture<ResponseEntity<String>> submitForm(@Valid @RequestBody ContactFormData formData, HttpServletRequest request) {
|
||||
logger.debug("submitForm");
|
||||
logger.trace(formData.toString());
|
||||
|
||||
return FormRequestHandler.handleRequestWithVerifyMail(formData, request.getRemoteAddr());
|
||||
}
|
||||
}
|
||||
|
||||
+38
-23
@@ -1,12 +1,18 @@
|
||||
package com.alttd.forms.contact;
|
||||
package com.alttd.forms.controlers.contact;
|
||||
|
||||
import com.alttd.forms.form.Form;
|
||||
import com.alttd.forms.mail.mail_forms.Mailable;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import jakarta.validation.constraints.Email;
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
import jakarta.validation.constraints.Pattern;
|
||||
import org.hibernate.validator.constraints.Length;
|
||||
|
||||
public class ContactFormData extends Form {
|
||||
import java.util.Optional;
|
||||
|
||||
public class ContactFormData extends Form implements Mailable {
|
||||
|
||||
public ContactFormData() {}
|
||||
|
||||
public ContactFormData(String username, String email, String question) {
|
||||
this.username = username;
|
||||
@@ -17,16 +23,17 @@ public class ContactFormData extends Form {
|
||||
@NotEmpty(message = "You have to provide a username")
|
||||
@Length(min = 3, max = 16, message = "Usernames have to be between 3 and 16 characters")
|
||||
@Pattern(regexp = "[a-zA-Z-0-9_]{3,16}", message = "Your username has to be a valid Minecraft username")
|
||||
public final String username;
|
||||
public String username;
|
||||
|
||||
@NotEmpty(message = "You have to provide an e-mail address")
|
||||
@Email(regexp = "(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|\"(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x21\\x23-\\x5b\\x5d-\\x7f]|\\\\[\\x01-\\x09\\x0b\\x0c\\x0e-\\x7f])*\")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-z0-9-]*[a-z0-9]:(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x21-\\x5a\\x53-\\x7f]|\\\\[\\x01-\\x09\\x0b\\x0c\\x0e-\\x7f])+)])",
|
||||
message = "This is not a valid e-mail address")
|
||||
public final String email;
|
||||
public String email;
|
||||
|
||||
@Length(min = 11, max = 2000, message = "Your question should have between 10 and 2000 characters")
|
||||
public final String question;
|
||||
public String question;
|
||||
|
||||
@JsonIgnore
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ContactFormData{" +
|
||||
@@ -36,28 +43,36 @@ public class ContactFormData extends Form {
|
||||
'}';
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
@Override
|
||||
public Optional<String> getDiscordBotUrl() {
|
||||
return Optional.of("http://discordbot:8001/api/contact/submitContactForm");
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
@Override
|
||||
public String getReceiver() {
|
||||
return "[email protected]";
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
@Override
|
||||
public String getSubject() {
|
||||
return "Contact form: " + username;
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
@Override
|
||||
public String getSender() {
|
||||
return email;
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
@Override
|
||||
public String toHtml() {
|
||||
StringBuilder htmlOutput = new StringBuilder();
|
||||
|
||||
htmlOutput.append("<table style='border-collapse: collapse; width: 100%;'>");
|
||||
|
||||
String[] fields = {"Username", "Email", "Question"};
|
||||
String[] values = {username, email, question};
|
||||
|
||||
for (int i = 0; i < fields.length; i++) {
|
||||
htmlOutput.append("<tr style='border: 1px solid #ddd;'>");
|
||||
htmlOutput.append("<td style='border: 1px solid #ddd; padding: 10px; font-weight: bold;'>");
|
||||
htmlOutput.append(fields[i]);
|
||||
htmlOutput.append("</td>");
|
||||
htmlOutput.append("<td style='border: 1px solid #ddd; padding: 10px;'>");
|
||||
htmlOutput.append(values[i]);
|
||||
htmlOutput.append("</td>");
|
||||
htmlOutput.append("</tr>");
|
||||
}
|
||||
|
||||
htmlOutput.append("</table>");
|
||||
|
||||
return htmlOutput.toString();
|
||||
return toHtml(fields, values);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.alttd.forms.controlers.event_apply;
|
||||
|
||||
import com.alttd.forms.controlers.FormRequestHandler;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.validation.Valid;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/event-apply")
|
||||
public class EventAppController {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(EventAppController.class);
|
||||
|
||||
@PostMapping("/eventApplication")
|
||||
public CompletableFuture<ResponseEntity<String>> submitForm(@Valid @RequestBody EventAppFormData formData, HttpServletRequest request) {
|
||||
logger.debug("submitForm");
|
||||
logger.trace(formData.toString());
|
||||
|
||||
return FormRequestHandler.handleRequestWithVerifyMail(formData, request.getRemoteAddr());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
package com.alttd.forms.controlers.event_apply;
|
||||
|
||||
import com.alttd.forms.form.Form;
|
||||
import com.alttd.forms.mail.mail_forms.Mailable;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import jakarta.validation.constraints.*;
|
||||
import org.hibernate.validator.constraints.Length;
|
||||
import org.hibernate.validator.constraints.Range;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
public class EventAppFormData extends Form implements Mailable {
|
||||
|
||||
public EventAppFormData() {}
|
||||
|
||||
public EventAppFormData(String username, String email, String discord, int age, String pronoun, int avg_time, String event_experience, String discord_vc, String good_builds, String bad_builds, String technical_aspects, String other) {
|
||||
this.username = username;
|
||||
this.email = email;
|
||||
this.discord = discord;
|
||||
this.age = age;
|
||||
this.pronoun = pronoun;
|
||||
this.avg_time = avg_time;
|
||||
this.event_experience = event_experience;
|
||||
this.discord_vc = discord_vc;
|
||||
this.good_builds = good_builds;
|
||||
this.bad_builds = bad_builds;
|
||||
this.technical_aspects = technical_aspects;
|
||||
this.other = other;
|
||||
}
|
||||
|
||||
@NotEmpty(message = "Username is required")
|
||||
@Length(min = 3, max = 16, message = "Username should be between 3 and 16 characters")
|
||||
@Pattern(regexp = "^[a-zA-Z0-9_]*$", message = "Username should only include alphanumeric characters and underscore")
|
||||
public String username;
|
||||
|
||||
@NotEmpty(message = "E-mail address is required")
|
||||
@Email(message = "Invalid email")
|
||||
@Length(min = 3, max = 254, message = "Email should be between 3 and 254 characters")
|
||||
public String email;
|
||||
|
||||
@NotEmpty(message = "Discord name is required")
|
||||
@Length(min = 2, max = 32, message = "Discord name should be between 2 and 32 characters")
|
||||
@Pattern(regexp = "^([a-z0-9._]{2,32})$", message = "Please enter a valid Discord name")
|
||||
public String discord;
|
||||
|
||||
@Min(value = 0, message = "Please enter a valid age")
|
||||
@Max(value = 999, message = "We do not accept players older than 999 years old sorry!")
|
||||
public int age;
|
||||
|
||||
@Length(max = 16, message = "Pronouns can't be longer than 16 characters")
|
||||
public String pronoun;
|
||||
|
||||
@Range(min = 0, max = 168, message = "The only valid values are 0-168")
|
||||
public int avg_time;
|
||||
|
||||
@NotEmpty(message = "Event experience is required")
|
||||
@Length(min = 2, max = 2000, message = "Experience should be between 2 and 2000 characters")
|
||||
public String event_experience;
|
||||
|
||||
@NotEmpty(message = "An answer is required")
|
||||
@Length(min = 2, max = 3, message = "Please answer yes or no")
|
||||
@Pattern(regexp = "(?i)^(yes|no)$", message = "Yes or no")
|
||||
public String discord_vc;
|
||||
|
||||
@NotEmpty(message = "Good build experience is required")
|
||||
@Length(min = 2, max = 2000, message = "Good build experience should be between 2 and 2000 characters")
|
||||
public String good_builds;
|
||||
|
||||
@NotEmpty(message = "Event experience is required")
|
||||
@Length(min = 2, max = 2000, message = "Bad build experience between 2 and 2000 characters")
|
||||
public String bad_builds;
|
||||
|
||||
@NotEmpty(message = "Technical aspects are required")
|
||||
@Length(min = 2, max = 2000, message = "Technical aspects should be between 2 and 2000 characters")
|
||||
public String technical_aspects;
|
||||
|
||||
@Length(max = 2000, message = "Text can't be longer than 2000 characters")
|
||||
public String other;
|
||||
|
||||
@JsonIgnore
|
||||
@Override
|
||||
public String toString() {
|
||||
return "EventAppFormData{" +
|
||||
"username='" + username + '\'' +
|
||||
", email='" + email + '\'' +
|
||||
", discord='" + discord + '\'' +
|
||||
", age=" + age +
|
||||
", pronoun='" + pronoun + '\'' +
|
||||
", avg_time=" + avg_time +
|
||||
", event_experience='" + event_experience + '\'' +
|
||||
", discord_vc='" + discord_vc + '\'' +
|
||||
", good_builds='" + good_builds + '\'' +
|
||||
", bad_builds='" + bad_builds + '\'' +
|
||||
", technical_aspects='" + technical_aspects + '\'' +
|
||||
", other='" + other + '\'' +
|
||||
'}';
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
@Override
|
||||
public Optional<String> getDiscordBotUrl() {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
@Override
|
||||
public String getReceiver() {
|
||||
return "[email protected]";
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
@Override
|
||||
public String getSubject() {
|
||||
return "Event Application: " + username;
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
@Override
|
||||
public String getSender() {
|
||||
return email;
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
@Override
|
||||
public String toHtml() {
|
||||
String[] fields = {"Username", "Email", "Discord", "Age", "Pronoun", "Avg time", "Event experience", "Discord VC", "Good builds", "Bad builds", "Technical aspects", "Other"};
|
||||
String[] values = {username, email, discord, String.valueOf(age), pronoun, String.valueOf(avg_time), event_experience, discord_vc, good_builds, bad_builds, technical_aspects, other};
|
||||
|
||||
return toHtml(fields, values);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
package com.alttd.forms.controlers.form_active;
|
||||
|
||||
public record BooleanResponse(boolean isActive) {
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package com.alttd.forms.controlers.form_active;
|
||||
|
||||
import com.alttd.forms.form.FormSettingsQuery;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import jakarta.validation.Valid;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/checks")
|
||||
public class FormActiveController {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(FormActiveController.class);
|
||||
|
||||
@PostMapping("/formActive")
|
||||
public CompletableFuture<ResponseEntity<String>> formActiveRequest(@Valid @RequestBody FormActiveData formData) {
|
||||
logger.debug("formActive");
|
||||
logger.trace(formData.toString());
|
||||
|
||||
FormSettingsQuery formSettingsQuery = new FormSettingsQuery();
|
||||
return formSettingsQuery
|
||||
.isActive(formData.formName)
|
||||
.thenApply(result -> {
|
||||
try {
|
||||
if (result.isEmpty()) {
|
||||
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Failed to check if the form is active");
|
||||
} else if (result.get()) {
|
||||
return ResponseEntity.ok(new ObjectMapper().writeValueAsString(new BooleanResponse(true)));
|
||||
} else {
|
||||
return ResponseEntity.ok(new ObjectMapper().writeValueAsString(new BooleanResponse(false)));
|
||||
}
|
||||
} catch (JsonProcessingException e) {
|
||||
logger.error("Failed to parse BooleanResponse", e);
|
||||
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Failed to respond");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.alttd.forms.controlers.form_active;
|
||||
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
import jakarta.validation.constraints.Pattern;
|
||||
import org.hibernate.validator.constraints.Length;
|
||||
|
||||
public class FormActiveData {
|
||||
|
||||
public FormActiveData() {}
|
||||
|
||||
public FormActiveData(String formName) {
|
||||
this.formName = formName;
|
||||
}
|
||||
|
||||
@NotEmpty(message = "You have to provide a form name")
|
||||
@Length(min = 1, max = 64, message = "Usernames have to be between 3 and 16 characters")
|
||||
@Pattern(regexp = "[a-zA-Z]{1,64}", message = "This is an invalid form name")
|
||||
public String formName;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.alttd.forms.controlers.global_rate_limiter;
|
||||
|
||||
import org.springframework.boot.web.servlet.FilterRegistrationBean;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@Configuration
|
||||
public class FilterConfig {
|
||||
|
||||
@Bean
|
||||
public FilterRegistrationBean<GlobalRateLimitingFilter> rateLimitingFilter() {
|
||||
FilterRegistrationBean<GlobalRateLimitingFilter> registrationBean = new FilterRegistrationBean<>();
|
||||
registrationBean.setFilter(new GlobalRateLimitingFilter());
|
||||
registrationBean.addUrlPatterns("/api/*");
|
||||
return registrationBean;
|
||||
}
|
||||
}
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
package com.alttd.forms.controlers.global_rate_limiter;
|
||||
|
||||
import jakarta.servlet.Filter;
|
||||
import jakarta.servlet.FilterChain;
|
||||
import jakarta.servlet.FilterConfig;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.ServletRequest;
|
||||
import jakarta.servlet.ServletResponse;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.time.Instant;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
@Component
|
||||
public class GlobalRateLimitingFilter implements Filter {
|
||||
|
||||
private static final int MAX_REQUESTS_PER_MINUTE = 30;
|
||||
private final Map<String, RequestCounter> ipRequestMap = new ConcurrentHashMap<>();
|
||||
|
||||
@Override
|
||||
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
|
||||
HttpServletRequest httpRequest = (HttpServletRequest) request;
|
||||
HttpServletResponse httpResponse = (HttpServletResponse) response;
|
||||
String clientIp = httpRequest.getRemoteAddr();
|
||||
|
||||
RequestCounter requestCounter = ipRequestMap.computeIfAbsent(clientIp, k -> new RequestCounter(0, Instant.now().getEpochSecond()));
|
||||
|
||||
synchronized (requestCounter) {
|
||||
long currentTime = Instant.now().getEpochSecond();
|
||||
if (currentTime - requestCounter.timestamp > 60) {
|
||||
// Reset counter every 60 seconds
|
||||
requestCounter.timestamp = currentTime;
|
||||
requestCounter.count.set(0);
|
||||
}
|
||||
|
||||
if (requestCounter.count.incrementAndGet() > MAX_REQUESTS_PER_MINUTE) {
|
||||
httpResponse.setStatus(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
chain.doFilter(request, response);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(FilterConfig filterConfig) throws ServletException {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
}
|
||||
|
||||
private static class RequestCounter {
|
||||
AtomicInteger count;
|
||||
long timestamp;
|
||||
|
||||
public RequestCounter(int count, long timestamp) {
|
||||
this.count = new AtomicInteger(count);
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,12 +8,51 @@ import java.sql.SQLException;
|
||||
|
||||
public class Database {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(Database.class); //TODO can be replaced with library and that one db file that creates the db
|
||||
private static final Logger logger = LoggerFactory.getLogger(Database.class);
|
||||
|
||||
public static void createTables() {
|
||||
String[] createTables = {
|
||||
"CREATE TABLE IF NOT EXISTS verify_form (e_mail VARCHAR(256), verification_code INT, formId INT, PRIMARY KEY(e_mail, verification_code))",
|
||||
"CREATE TABLE IF NOT EXISTS form (formId INT AUTO_INCREMENT, creation_date BIGINT, form_json TEXT, form_class VARCHAR(64), PRIMARY KEY(formId))"
|
||||
// language=SQL
|
||||
"""
|
||||
CREATE TABLE IF NOT EXISTS verify_form(
|
||||
e_mail VARCHAR(256) NOT NULL,
|
||||
verification_code INT NOT NULL,
|
||||
formId INT NOT NULL,
|
||||
PRIMARY KEY(e_mail, verification_code)
|
||||
)
|
||||
""",
|
||||
// language=SQL
|
||||
"""
|
||||
CREATE TABLE IF NOT EXISTS form(
|
||||
formId INT AUTO_INCREMENT NOT NULL,
|
||||
creation_date BIGINT NOT NULL,
|
||||
form_json TEXT NOT NULL,
|
||||
form_class VARCHAR(64) NOT NULL,
|
||||
ip VARCHAR(45) not null,
|
||||
time TIMESTAMP default current_timestamp() not null,
|
||||
PRIMARY KEY(formId)
|
||||
)
|
||||
""",
|
||||
// language=SQL
|
||||
"""
|
||||
CREATE TABLE IF NOT EXISTS rate_limit(
|
||||
id INT AUTO_INCREMENT NOT NULL,
|
||||
time TIMESTAMP NOT NULL,
|
||||
ip VARCHAR(45) NOT NULL,
|
||||
mail VARCHAR(256) NOT NULL,
|
||||
PRIMARY KEY(id)
|
||||
)
|
||||
""",
|
||||
// language=SQL
|
||||
"""
|
||||
CREATE TABLE IF NOT EXISTS form_active(
|
||||
id INT AUTO_INCREMENT NOT NULL,
|
||||
name VARCHAR(64) NOT NULL,
|
||||
active_from TIMESTAMP NOT NULL,
|
||||
active_until TIMESTAMP NOT NULL,
|
||||
PRIMARY KEY(id)
|
||||
)
|
||||
"""
|
||||
};
|
||||
Connection connection = DatabaseConnection.getConnection();
|
||||
for (String query : createTables) {
|
||||
|
||||
@@ -1,15 +1,45 @@
|
||||
package com.alttd.forms.form;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public abstract class Form {
|
||||
|
||||
public String toJsonString() {
|
||||
return new Gson().toJson(this);
|
||||
public String toJsonString() throws JsonProcessingException {
|
||||
ObjectMapper objectMapper = new ObjectMapper();
|
||||
objectMapper.registerModule(new JavaTimeModule());
|
||||
return objectMapper.writeValueAsString(this);
|
||||
}
|
||||
|
||||
public abstract String toHtml();
|
||||
|
||||
public String toHtml(String[] fields, String[] values) {
|
||||
StringBuilder htmlOutput = new StringBuilder();
|
||||
for (int i = 0; i < fields.length; i++) {
|
||||
String paragraphs = Arrays.stream(values[i].split("\n"))
|
||||
.filter(str -> !str.isBlank())
|
||||
.map(str -> String.format("<p>%s</p>", str))
|
||||
.collect(Collectors.joining("\n"));
|
||||
htmlOutput.append(
|
||||
String.format(
|
||||
//language=HTML
|
||||
"""
|
||||
<p><strong>%s</strong></p>
|
||||
<p>%s</p>
|
||||
""", fields[i], paragraphs));
|
||||
}
|
||||
return htmlOutput.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public abstract String toString();
|
||||
|
||||
public abstract Optional<String> getDiscordBotUrl();
|
||||
|
||||
public abstract String getSender();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
package com.alttd.forms.form;
|
||||
|
||||
import com.alttd.forms.database.DatabaseConnection;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.time.Instant;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
public class FormSettingsQuery {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(FormSettingsQuery.class);
|
||||
|
||||
public CompletableFuture<Optional<Boolean>> isActive(@NotNull String formName) {
|
||||
return CompletableFuture.supplyAsync(() -> {
|
||||
Connection connection = DatabaseConnection.getConnection();
|
||||
String sql = "SELECT active_from, active_until FROM form_active WHERE name = ?";
|
||||
try (PreparedStatement stmt = connection.prepareStatement(sql)) {
|
||||
stmt.setString(1, formName);
|
||||
ResultSet resultSet = stmt.executeQuery();
|
||||
if (!resultSet.next()) {
|
||||
return Optional.of(true);
|
||||
}
|
||||
Instant activeFrom = resultSet.getTimestamp("active_from").toInstant();
|
||||
Instant activeUntil = resultSet.getTimestamp("active_until").toInstant();
|
||||
Instant now = Instant.now();
|
||||
if (activeFrom.isAfter(now) || activeUntil.isBefore(now))
|
||||
return Optional.of(false);
|
||||
return Optional.of(true);
|
||||
} catch (SQLException e) {
|
||||
logger.error("Failed to check if the {} form is active", formName ,e);
|
||||
return Optional.empty();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
+14
-10
@@ -1,4 +1,4 @@
|
||||
package com.alttd.forms.contact;
|
||||
package com.alttd.forms.form;
|
||||
|
||||
import com.alttd.forms.database.DatabaseConnection;
|
||||
|
||||
@@ -8,7 +8,7 @@ import java.util.Optional;
|
||||
import java.util.Random;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import com.alttd.forms.form.Form;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -21,26 +21,30 @@ public class StoreFormQuery {
|
||||
return 100000 + random.nextInt(900000);
|
||||
}
|
||||
|
||||
private Optional<Long> insertForm(Connection connection, Form form) {
|
||||
String insertForm = "INSERT INTO form (creation_date, form_json, form_class) VALUES (?, ?, ?)";
|
||||
private Optional<Long> insertForm(Connection connection, Form form, String ip) {
|
||||
String insertForm = "INSERT INTO form (creation_date, form_json, form_class, ip) VALUES (?, ?, ?, ?)";
|
||||
try (PreparedStatement stmt = connection.prepareStatement(insertForm, Statement.RETURN_GENERATED_KEYS)) {
|
||||
stmt.setLong(1, Instant.now().toEpochMilli());
|
||||
stmt.setString(2, form.toJsonString());
|
||||
stmt.setString(3, form.getClass().getSimpleName());
|
||||
stmt.setString(4, ip);
|
||||
int affectedRows = stmt.executeUpdate();
|
||||
if (affectedRows == 0) {
|
||||
logger.error("No rows affected during insert of form: " + form);
|
||||
logger.error("No rows affected during insert of form: {}", form);
|
||||
return Optional.empty();
|
||||
}
|
||||
ResultSet generatedKeys = stmt.getGeneratedKeys();
|
||||
if (generatedKeys.next()) {
|
||||
return Optional.of(generatedKeys.getLong(1));
|
||||
} else {
|
||||
logger.error("No primary key generated when inserting form " + form);
|
||||
logger.error("No primary key generated when inserting form {}", form);
|
||||
return Optional.empty();
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
logger.error("Failed insert form query for: " + form, e);
|
||||
logger.error("Failed insert form query for: {}", form, e);
|
||||
return Optional.empty();
|
||||
} catch (JsonProcessingException e) {
|
||||
logger.error("Invalid class for JSON, failed insert form query for: {}", form, e);
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
@@ -55,17 +59,17 @@ public class StoreFormQuery {
|
||||
stmt.executeUpdate();
|
||||
return Optional.of(verificationCode);
|
||||
} catch (SQLException e) {
|
||||
logger.error("Failed to insert verification code for form with id: " + formId);
|
||||
logger.error("Failed to insert verification code for form with id: {}", formId);
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
public CompletableFuture<Integer> storeFormForVerificationCode(String eMail, Form form) {
|
||||
public CompletableFuture<Integer> storeFormForVerificationCode(String eMail, Form form, String ip) {
|
||||
logger.debug("storeFormForVerificationCode");
|
||||
Connection connection = DatabaseConnection.getConnection();
|
||||
logger.trace("Connection: {}", connection);
|
||||
return CompletableFuture.supplyAsync(() -> {
|
||||
Optional<Long> optionalFormId = insertForm(connection, form);
|
||||
Optional<Long> optionalFormId = insertForm(connection, form, ip);
|
||||
logger.trace("Form id: {}", optionalFormId);
|
||||
if (optionalFormId.isEmpty()) {
|
||||
throw new RuntimeException("Failed to store form");
|
||||
@@ -15,7 +15,8 @@ public class MailForm {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(MailForm.class);
|
||||
|
||||
public static void sendForm(String receiver, Form form) {
|
||||
public static void sendForm(Form form, Mailable mailable) {
|
||||
String receiver = mailable.getReceiver();
|
||||
Properties mailProperties = MailSettings.getMailProperties();
|
||||
Optional<PasswordAuthentication> accountDetails = MailSettings.getAccountDetails();
|
||||
if (accountDetails.isEmpty()) {
|
||||
@@ -32,8 +33,7 @@ public class MailForm {
|
||||
Message.RecipientType.TO,
|
||||
InternetAddress.parse(receiver)
|
||||
);
|
||||
message.setSubject("Altitude Form");
|
||||
//TODO add something above the html form probably
|
||||
message.setSubject(mailable.getSubject());
|
||||
message.setContent(form.toHtml(), "text/html");
|
||||
try {
|
||||
Transport.send(message);
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.alttd.forms.mail.mail_forms;
|
||||
|
||||
public interface Mailable {
|
||||
|
||||
String getReceiver();
|
||||
|
||||
String getSubject();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
package com.alttd.forms.mail.rate_limitter;
|
||||
|
||||
import com.alttd.forms.database.DatabaseConnection;
|
||||
import com.alttd.forms.properties.PropertiesLoader;
|
||||
import com.alttd.forms.properties.PropertiesWriter;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.sql.SQLException;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.Optional;
|
||||
import java.util.Properties;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
public class FormRateLimit {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(FormRateLimit.class);
|
||||
private static FormRateLimit instance;
|
||||
private Properties properties;
|
||||
|
||||
private FormRateLimit(String path) {
|
||||
FormRateLimit.instance = this;
|
||||
loadProperties(path);
|
||||
}
|
||||
|
||||
public static FormRateLimit getInstance() {
|
||||
return FormRateLimit.instance;
|
||||
}
|
||||
|
||||
public static void initialize(String path) {
|
||||
if (instance != null)
|
||||
return;
|
||||
FormRateLimit.instance = new FormRateLimit(path);
|
||||
}
|
||||
|
||||
private void loadProperties(String path) {
|
||||
String fileName = "form.properties";
|
||||
Optional<Properties> optionalProperties = PropertiesLoader.loadProperties(path, fileName);
|
||||
if (optionalProperties.isPresent()) {
|
||||
properties = optionalProperties.get();
|
||||
return;
|
||||
}
|
||||
|
||||
properties = new Properties();
|
||||
properties.setProperty("rate-limit-ip-count", "10");
|
||||
properties.setProperty("rate-limit-mail-count", "10");
|
||||
properties.setProperty("rate-limit-ip-minutes", "60");
|
||||
properties.setProperty("rate-limit-mail-minutes", "60");
|
||||
PropertiesWriter.writeProperties(properties, path, fileName);
|
||||
}
|
||||
|
||||
public CompletableFuture<Optional<Boolean>> isRateLimited(String ip, String mail) {
|
||||
return CompletableFuture.supplyAsync(
|
||||
() -> {
|
||||
RateLimitQuery rateLimitQuery = new RateLimitQuery(DatabaseConnection.getConnection());
|
||||
try {
|
||||
if (isIpRateLimited(rateLimitQuery, ip) || isMailRateLimited(rateLimitQuery, mail))
|
||||
return Optional.of(true);
|
||||
} catch (SQLException e) {
|
||||
logger.error("Failed rate limit query for ip: {}, mail: {}", ip, mail, e);
|
||||
return Optional.empty();
|
||||
} catch (NumberFormatException e) {
|
||||
logger.error("Failed loading numbers from properties", e);
|
||||
return Optional.empty();
|
||||
}
|
||||
return Optional.of(false);
|
||||
});
|
||||
}
|
||||
|
||||
private boolean isIpRateLimited(RateLimitQuery rateLimitQuery, String ip) throws SQLException, NumberFormatException {
|
||||
int minutes = Integer.parseInt(properties.getProperty("rate-limit-ip-minutes"));
|
||||
Instant after = Instant.now().minus(Duration.ofMinutes(minutes));
|
||||
int ipHits = rateLimitQuery.getIpHits(ip, after);
|
||||
int ipRateLimit = Integer.parseInt(properties.getProperty("rate-limit-ip-count"));
|
||||
return ipHits >= ipRateLimit;
|
||||
}
|
||||
|
||||
private boolean isMailRateLimited(RateLimitQuery rateLimitQuery, String mail) throws SQLException, NumberFormatException {
|
||||
int minutes = Integer.parseInt(properties.getProperty("rate-limit-mail-minutes"));
|
||||
Instant after = Instant.now().minus(Duration.ofMinutes(minutes));
|
||||
int ipHits = rateLimitQuery.getMailHits(mail, after);
|
||||
int ipRateLimit = Integer.parseInt(properties.getProperty("rate-limit-mail-count"));
|
||||
return ipHits >= ipRateLimit;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package com.alttd.forms.mail.rate_limitter;
|
||||
|
||||
import java.time.Instant;
|
||||
|
||||
public record RateLimitEntryDTO(Instant time, String ip, String mail) {
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package com.alttd.forms.mail.rate_limitter;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.sql.*;
|
||||
import java.time.Instant;
|
||||
|
||||
public class RateLimitQuery {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(RateLimitQuery.class);
|
||||
private final Connection connection;
|
||||
|
||||
protected RateLimitQuery(Connection connection) {
|
||||
this.connection = connection;
|
||||
}
|
||||
|
||||
protected int getIpHits(String ip, Instant after) throws SQLException {
|
||||
String sql = "SELECT COUNT(*) AS hits FROM rate_limit WHERE ip = ? AND time > ?";
|
||||
try (PreparedStatement stmt = connection.prepareStatement(sql)) {
|
||||
stmt.setString(1, ip);
|
||||
stmt.setTimestamp(2, Timestamp.from(after));
|
||||
ResultSet resultSet = stmt.executeQuery();
|
||||
if (!resultSet.next()) {
|
||||
return 0;
|
||||
}
|
||||
return resultSet.getInt("hits");
|
||||
} catch (SQLException e) {
|
||||
logger.error("Failed get ip hits query for ip: {}", ip, e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
protected int getMailHits(String mail, Instant after) throws SQLException {
|
||||
String sql = "SELECT COUNT(*) AS hits FROM rate_limit WHERE mail = ? AND time > ?";
|
||||
try (PreparedStatement stmt = connection.prepareStatement(sql)) {
|
||||
stmt.setString(1, mail);
|
||||
stmt.setTimestamp(2, Timestamp.from(after));
|
||||
ResultSet resultSet = stmt.executeQuery();
|
||||
if (!resultSet.next()) {
|
||||
return 0;
|
||||
}
|
||||
return resultSet.getInt("hits");
|
||||
} catch (SQLException e) {
|
||||
logger.error("Failed get mail hits query for ip: {}", mail, e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
protected boolean insertRateLimitEntry(RateLimitEntryDTO entry) throws SQLException {
|
||||
String sql = "INSERT INTO rate_limit (time, ip, mail) VALUES (?, ?, ?)";
|
||||
try {
|
||||
PreparedStatement stmt = connection.prepareStatement(sql);
|
||||
stmt.setTimestamp(1, Timestamp.from(entry.time()));
|
||||
stmt.setString(2, entry.ip());
|
||||
stmt.setString(3, entry.mail());
|
||||
return stmt.executeUpdate() > 0;
|
||||
} catch (SQLException e) {
|
||||
logger.error("Failed to store rate limit for ip: {}, mail: {}, time: {}", entry.ip(), entry.mail(), entry.time(), e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
package com.alttd.forms.mail.verification;
|
||||
|
||||
public enum VerificationResult {
|
||||
NO_MAIL_ACCOUNT, FAILED_TO_SEND, VERIFICATION_SENT,
|
||||
NO_MAIL_ACCOUNT, FAILED_TO_SEND, VERIFICATION_SENT, RATE_LIMIT_EXCEEDED, FAILED_TO_RETRIEVE_RATE_LIMIT_DATA;
|
||||
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package com.alttd.forms.mail.verification;
|
||||
|
||||
import com.alttd.forms.form.Form;
|
||||
import com.alttd.forms.mail.MailSettings;
|
||||
import com.alttd.forms.mail.rate_limitter.FormRateLimit;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -15,7 +17,7 @@ public class Verify {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(Verify.class);
|
||||
|
||||
public static CompletableFuture<VerificationResult> verifyEmail(String address, int code) {
|
||||
public static CompletableFuture<VerificationResult> verifyEmail(String ip, String address, int code, Form form) {
|
||||
logger.debug("verifyEmail");
|
||||
Properties mailProperties = MailSettings.getMailProperties();
|
||||
logger.debug("mailProperties: {}", mailProperties);
|
||||
@@ -30,35 +32,62 @@ public class Verify {
|
||||
|
||||
//TODO rate limit sending mail from IP and to specific e-mail addresses (max 1 per minute and max 10 per day)
|
||||
//TODO include a link to all emails that people can click to block us from sending mail to them so no one can use us to spam ppl
|
||||
return CompletableFuture.supplyAsync(() -> sendMail(ip, address, code, session, passwordAuthentication, form));
|
||||
}
|
||||
|
||||
private static VerificationResult sendMail(String ip, String address, int code, Session session, PasswordAuthentication passwordAuthentication, Form form) {
|
||||
Optional<Boolean> isRateLimited = FormRateLimit.getInstance().isRateLimited(ip, address).join();
|
||||
if (isRateLimited.isEmpty()) {
|
||||
return VerificationResult.FAILED_TO_RETRIEVE_RATE_LIMIT_DATA;
|
||||
} else if (isRateLimited.get()) {
|
||||
return VerificationResult.RATE_LIMIT_EXCEEDED;
|
||||
}
|
||||
|
||||
Message message;
|
||||
try {
|
||||
logger.trace("Creating mail");
|
||||
Message message = new MimeMessage(session);
|
||||
message.setFrom(new InternetAddress(passwordAuthentication.getUserName()));
|
||||
logger.trace("Set from to {}", passwordAuthentication.getUserName());
|
||||
message.setRecipients(
|
||||
Message.RecipientType.TO,
|
||||
InternetAddress.parse(address)
|
||||
);
|
||||
logger.trace("Set recipients");
|
||||
message.setSubject("Altitude Email Verification");
|
||||
message.setText("Please verify your email by entering the following code on the page you made the form in\n" + code); //TODO pretty html stuff
|
||||
logger.trace("Set code: {}", code);
|
||||
//TODO include the form they filled in (also in pretty html stuff)
|
||||
return CompletableFuture.supplyAsync(() -> {
|
||||
try {
|
||||
logger.trace("Sending mail");
|
||||
Transport.send(message);
|
||||
logger.trace("Sending mail succeeded");
|
||||
return VerificationResult.VERIFICATION_SENT;
|
||||
} catch (MessagingException e) {
|
||||
logger.error("Failed to send mail", e);
|
||||
return VerificationResult.FAILED_TO_SEND;
|
||||
}
|
||||
});
|
||||
message = getMail(address, code, session, passwordAuthentication, form);
|
||||
} catch (MessagingException e) {
|
||||
logger.error("Failed to create MimeMessage", e);
|
||||
return CompletableFuture.completedFuture(VerificationResult.FAILED_TO_SEND);
|
||||
return VerificationResult.FAILED_TO_SEND;
|
||||
}
|
||||
|
||||
try {
|
||||
logger.trace("Sending mail");
|
||||
Transport.send(message);
|
||||
logger.trace("Sending mail succeeded");
|
||||
return VerificationResult.VERIFICATION_SENT;
|
||||
} catch (MessagingException e) {
|
||||
logger.error("Failed to send mail", e);
|
||||
return VerificationResult.FAILED_TO_SEND;
|
||||
}
|
||||
}
|
||||
|
||||
private static Message getMail(String address, int code, Session session, PasswordAuthentication passwordAuthentication, Form form) throws MessagingException {
|
||||
logger.trace("Creating mail");
|
||||
Message message = new MimeMessage(session);
|
||||
message.setFrom(new InternetAddress(passwordAuthentication.getUserName()));
|
||||
logger.trace("Set from to {}", passwordAuthentication.getUserName());
|
||||
message.setRecipients(
|
||||
Message.RecipientType.TO,
|
||||
InternetAddress.parse(address)
|
||||
);
|
||||
logger.trace("Set recipients");
|
||||
message.setSubject("Altitude Email Verification");
|
||||
String body = String.format(
|
||||
//language=HTML
|
||||
"""
|
||||
<title>
|
||||
Email Verification
|
||||
</title>
|
||||
<p>Please verify your email by entering the following code on the page you made the form in:</p>
|
||||
<p>%s</p>
|
||||
<p></p>
|
||||
<p>This is the form you're submitting:</p>
|
||||
""", code);
|
||||
body += form.toHtml();
|
||||
message.setContent(body, "text/html");
|
||||
logger.trace("Set code: {}", code);
|
||||
return message;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ public class PropertiesLoader {
|
||||
}
|
||||
file = new File(currentJarPath.get(), fileName);
|
||||
} else {
|
||||
file = Path.of(URI.create("file://" + path + File.separator + fileName)).toFile();
|
||||
file = new File(path, fileName);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ public class PropertiesWriter {
|
||||
}
|
||||
file = new File(currentJarPath.get(), fileName);
|
||||
} else {
|
||||
file = Path.of(URI.create("file://" + path + File.separator + fileName)).toFile();
|
||||
file = new File(path, fileName);
|
||||
}
|
||||
|
||||
if (file.exists()) {
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
package com.alttd.forms.verify_mail;
|
||||
|
||||
import com.alttd.forms.contact.ContactFormData;
|
||||
import com.alttd.forms.controlers.apply.StaffAppFormData;
|
||||
import com.alttd.forms.controlers.contact.ContactFormData;
|
||||
import com.alttd.forms.controlers.event_apply.EventAppFormData;
|
||||
import com.alttd.forms.database.DatabaseConnection;
|
||||
import com.alttd.forms.form.Form;
|
||||
import com.google.gson.Gson;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -53,6 +57,9 @@ public class FormQuery {
|
||||
} catch (IllegalArgumentException e) {
|
||||
logger.error("Invalid form class in database", e);
|
||||
return Optional.empty();
|
||||
} catch (JsonProcessingException e) {
|
||||
logger.error("Invalid form json in database", e);
|
||||
return Optional.empty();
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
logger.error("Failed select form query for form with id: {}", formId, e);
|
||||
@@ -60,10 +67,18 @@ public class FormQuery {
|
||||
}
|
||||
}
|
||||
|
||||
private Form getForm(String className, String json) throws IllegalArgumentException {
|
||||
private Form getForm(String className, String json) throws IllegalArgumentException, JsonProcessingException {
|
||||
ObjectMapper objectMapper = new ObjectMapper();
|
||||
objectMapper.registerModule(new JavaTimeModule());
|
||||
switch (className) {
|
||||
case "ContactFormData" -> {
|
||||
return new Gson().fromJson(json, ContactFormData.class);
|
||||
return objectMapper.readValue(json, ContactFormData.class);
|
||||
}
|
||||
case "StaffAppFormData" -> {
|
||||
return objectMapper.readValue(json, StaffAppFormData.class);
|
||||
}
|
||||
case "EventAppFormData" -> {
|
||||
return objectMapper.readValue(json, EventAppFormData.class);
|
||||
}
|
||||
default -> throw new IllegalArgumentException("Invalid form class name: " + className);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
package com.alttd.forms.verify_mail;
|
||||
|
||||
import com.alttd.forms.form.Form;
|
||||
import com.alttd.forms.mail.mail_forms.MailForm;
|
||||
import com.alttd.forms.mail.mail_forms.Mailable;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import jakarta.validation.Valid;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -17,6 +20,7 @@ import java.net.http.HttpClient;
|
||||
import java.net.http.HttpRequest;
|
||||
import java.net.http.HttpResponse;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
@RestController
|
||||
@@ -31,35 +35,54 @@ public class VerifyController {
|
||||
logger.trace("verificationData: {}", verificationData);
|
||||
return new FormQuery().getFormForCode(verificationData.code, verificationData.eMail).thenApply(result -> result.form()
|
||||
.map(form -> {
|
||||
try {
|
||||
HttpRequest request = HttpRequest.newBuilder()
|
||||
//Discord bot url
|
||||
.uri(new URI("http://discordbot:8001/api/contact/submitContactForm"))//TODO get uri from form
|
||||
.header("Content-Type", "application/json;charset=UTF-8")
|
||||
.POST(HttpRequest.BodyPublishers.ofString(form.toJsonString(), StandardCharsets.UTF_8))
|
||||
.build();
|
||||
logger.trace("request: {}", request);
|
||||
HttpClient client = HttpClient.newHttpClient();
|
||||
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
|
||||
logger.trace("response: {}", response);
|
||||
if (response.statusCode() < 200 || response.statusCode() > 200) {
|
||||
logger.error(String.format("Failed to send form to Discord. Got status code [%d], with body\n%s", response.statusCode(), response.body()));
|
||||
//TODO handle failure
|
||||
//TODO strings to config
|
||||
return ResponseEntity.ok("Failed to send form to Discord, please contact us at [email protected]");
|
||||
Optional<String> discordBotUrl = form.getDiscordBotUrl();
|
||||
if (discordBotUrl.isPresent()){
|
||||
Optional<ResponseEntity<String>> stringResponseEntity = sendDiscordMessage(discordBotUrl.get(), form);
|
||||
if (stringResponseEntity.isPresent()){
|
||||
return stringResponseEntity.get();
|
||||
}
|
||||
} catch (URISyntaxException e) {
|
||||
logger.error("Unable to create URI for posting form", e); //TODO more clear
|
||||
return ResponseEntity.ok("Unable to create URI for posting form, please contact us at [email protected]");
|
||||
} catch (IOException | InterruptedException e) {
|
||||
logger.error("Unable to send form to Discord", e); //TODO more clear
|
||||
}
|
||||
if (form instanceof Mailable mailable)
|
||||
MailForm.sendForm(form, mailable);
|
||||
try {
|
||||
return ResponseEntity.ok(form.toJsonString());
|
||||
} catch (JsonProcessingException e) {
|
||||
logger.error("Unable to send form to Discord, invalid JSON", e);
|
||||
return ResponseEntity.ok("Unable to send form to Discord, please contact us at [email protected]");
|
||||
}
|
||||
MailForm.sendForm("[email protected]", form);
|
||||
return ResponseEntity.ok(form.toJsonString());
|
||||
})
|
||||
.orElse(ResponseEntity.ok(result.failReason()))
|
||||
).exceptionally(throwable -> ResponseEntity.internalServerError()
|
||||
.body("The server was unable to process your request, if this issue persists please contact [email protected]"));
|
||||
}
|
||||
|
||||
private Optional<ResponseEntity<String>> sendDiscordMessage(String discordBotUrl, Form form) {
|
||||
try {
|
||||
HttpRequest request = HttpRequest.newBuilder()
|
||||
.uri(new URI(discordBotUrl))
|
||||
.header("Content-Type", "application/json;charset=UTF-8")
|
||||
.POST(HttpRequest.BodyPublishers.ofString(form.toJsonString(), StandardCharsets.UTF_8))
|
||||
.build();
|
||||
logger.trace("request: {}", request);
|
||||
HttpClient client = HttpClient.newHttpClient();
|
||||
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
|
||||
logger.trace("response: {}", response);
|
||||
if (response.statusCode() < 200 || response.statusCode() > 200) {
|
||||
logger.error(String.format("Failed to send form to Discord. Got status code [%d], with body\n%s", response.statusCode(), response.body()));
|
||||
//TODO handle failure
|
||||
//TODO strings to config
|
||||
return Optional.of(ResponseEntity.ok("Failed to send form to Discord, please contact us at [email protected]"));
|
||||
}
|
||||
} catch (URISyntaxException e) {
|
||||
logger.error("Unable to create URI for posting form", e); //TODO more clear
|
||||
return Optional.of(ResponseEntity.ok("Unable to create URI for posting form, please contact us at [email protected]"));
|
||||
} catch (JsonProcessingException e) {
|
||||
logger.error("Unable to send form to Discord, invalid JSON", e);
|
||||
return Optional.of(ResponseEntity.ok("Unable to send form to Discord, please contact us at [email protected]"));
|
||||
} catch (IOException | InterruptedException e) {
|
||||
logger.error("Unable to send form to Discord", e); //TODO more clear
|
||||
return Optional.of(ResponseEntity.ok("Unable to send form to Discord, please contact us at [email protected]"));
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
@@ -1,2 +1,4 @@
|
||||
logging.level.com.alttd.forms=warn
|
||||
logging.level.com.alttd.forms=WARN
|
||||
server.port=8002
|
||||
spring.jackson.date-format=yyyy-MM-dd
|
||||
spring.jackson.serialization.WRITE_DATES_AS_TIMESTAMPS=false
|
||||
@@ -1,5 +1,5 @@
|
||||
import com.alttd.forms.contact.ContactFormData;
|
||||
import com.alttd.forms.contact.StoreFormQuery;
|
||||
import com.alttd.forms.controlers.contact.ContactFormData;
|
||||
import com.alttd.forms.form.StoreFormQuery;
|
||||
import com.alttd.forms.database.DatabaseConnection;
|
||||
import com.alttd.forms.form.Form;
|
||||
import com.alttd.forms.verify_mail.FormQuery;
|
||||
@@ -23,7 +23,7 @@ public class TestForm {
|
||||
Assertions.assertDoesNotThrow(() -> DatabaseConnection.initialize());
|
||||
StoreFormQuery storeFormQuery = new StoreFormQuery();
|
||||
ContactFormData contactFormData = new ContactFormData("akastijn", "[email protected]", "This is a test question.");
|
||||
storeFormQuery.storeFormForVerificationCode("[email protected]", contactFormData).
|
||||
storeFormQuery.storeFormForVerificationCode("[email protected]", contactFormData, "0.0.0.0").
|
||||
thenAccept(code -> {
|
||||
TestForm.code = code;
|
||||
Assertions.assertTrue(code > 999 && code < 10000);
|
||||
|
||||
Reference in New Issue
Block a user