diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml deleted file mode 100644 index e08023aad..000000000 Binary files a/.gitlab-ci.yml and /dev/null differ diff --git a/Dockerfile b/Dockerfile index 128d5b9ce..4f4eb221e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,7 @@ -FROM tomcat:9-jre8 +FROM tomcat:9-jdk11 RUN rm -rf /usr/local/tomcat/webapps/* -COPY build/2.4.5/BudgetMaster-v2.4.5.war $CATALINA_HOME/webapps/ROOT.war +COPY build/2.5.0/BudgetMaster-v2.5.0.war $CATALINA_HOME/webapps/ROOT.war COPY src/main/resources/config/templates/settings-docker.properties /root/.Deadlocker/BudgetMaster/settings.properties EXPOSE 8080 \ No newline at end of file diff --git a/README.md b/README.md index 0ce08f5c9..821e6792b 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ Manage your monthly budget easily with BudgetMaster - __start:__ 17.12.16 -- __current release:__ v2.4.5 (28) from 19.08.20 +- __current release:__ v2.5.0 (29) from 03.12.20 ## Key Features - Keep your data private - Host your own BudgetMaster server or use it in standalone mode. All data remains on your machines. @@ -16,7 +16,7 @@ Manage your monthly budget easily with BudgetMaster - Password protected website - Your data can only be accessed by entering the correct password. (Note: The database is not encrypted) - Localization - English and German supported. - Search and Filter - Search for individual transactions or filter your view. -- Visualize your data - Use on of the pre-defined charts or create your one by using the chart framework to visualize and analyze your data. +- Visualize your data - Use one of the pre-defined charts or create your one by using the chart framework to visualize and analyze your data. - Auto Backup - Schedule an automatic export of your database content. ## Available Languages diff --git a/build/logo/BudgetMaster Icon.blend b/build/logo/BudgetMaster Icon.blend deleted file mode 100644 index af1b14927..000000000 Binary files a/build/logo/BudgetMaster Icon.blend and /dev/null differ diff --git a/build/logo/Font.txt b/build/logo/Font.txt deleted file mode 100644 index 5a083d70f..000000000 --- a/build/logo/Font.txt +++ /dev/null @@ -1 +0,0 @@ -League Gothic \ No newline at end of file diff --git a/build/screenshots/dark/accounts.png b/build/screenshots/dark/accounts.png index 5bdc2063f..d653779ba 100644 Binary files a/build/screenshots/dark/accounts.png and b/build/screenshots/dark/accounts.png differ diff --git a/build/screenshots/dark/categories.png b/build/screenshots/dark/categories.png index 3ee17912a..bbddde71c 100644 Binary files a/build/screenshots/dark/categories.png and b/build/screenshots/dark/categories.png differ diff --git a/build/screenshots/dark/chart_1.png b/build/screenshots/dark/chart_1.png index 69a56d6b6..dc238ce7f 100644 Binary files a/build/screenshots/dark/chart_1.png and b/build/screenshots/dark/chart_1.png differ diff --git a/build/screenshots/dark/chart_2.png b/build/screenshots/dark/chart_2.png index 5380589e9..4ef472e4b 100644 Binary files a/build/screenshots/dark/chart_2.png and b/build/screenshots/dark/chart_2.png differ diff --git a/build/screenshots/dark/chart_3.png b/build/screenshots/dark/chart_3.png index a6ef90957..a6a9458b9 100644 Binary files a/build/screenshots/dark/chart_3.png and b/build/screenshots/dark/chart_3.png differ diff --git a/build/screenshots/dark/chart_4.png b/build/screenshots/dark/chart_4.png index 9d6ddf292..ea9de7795 100644 Binary files a/build/screenshots/dark/chart_4.png and b/build/screenshots/dark/chart_4.png differ diff --git a/build/screenshots/dark/filter_1.png b/build/screenshots/dark/filter_1.png index f3766d2bd..ae5f87d86 100644 Binary files a/build/screenshots/dark/filter_1.png and b/build/screenshots/dark/filter_1.png differ diff --git a/build/screenshots/dark/filter_2.png b/build/screenshots/dark/filter_2.png index be19f7e56..cad992f60 100644 Binary files a/build/screenshots/dark/filter_2.png and b/build/screenshots/dark/filter_2.png differ diff --git a/build/screenshots/dark/home.png b/build/screenshots/dark/home.png index 45a8b5db6..6a05cee43 100644 Binary files a/build/screenshots/dark/home.png and b/build/screenshots/dark/home.png differ diff --git a/build/screenshots/dark/hotkeys.png b/build/screenshots/dark/hotkeys.png index d8a673807..eda6c5de1 100644 Binary files a/build/screenshots/dark/hotkeys.png and b/build/screenshots/dark/hotkeys.png differ diff --git a/build/screenshots/dark/new_category.png b/build/screenshots/dark/new_category.png index 6865b5ac5..e4d659dba 100644 Binary files a/build/screenshots/dark/new_category.png and b/build/screenshots/dark/new_category.png differ diff --git a/build/screenshots/dark/new_normal_transaction.png b/build/screenshots/dark/new_normal_transaction.png index f1a6f9bfc..648a87fea 100644 Binary files a/build/screenshots/dark/new_normal_transaction.png and b/build/screenshots/dark/new_normal_transaction.png differ diff --git a/build/screenshots/dark/new_transaction_1.png b/build/screenshots/dark/new_transaction_1.png index 71d325577..e1341cd03 100644 Binary files a/build/screenshots/dark/new_transaction_1.png and b/build/screenshots/dark/new_transaction_1.png differ diff --git a/build/screenshots/dark/new_transaction_2.png b/build/screenshots/dark/new_transaction_2.png index 4ab0b885f..c439a06c6 100644 Binary files a/build/screenshots/dark/new_transaction_2.png and b/build/screenshots/dark/new_transaction_2.png differ diff --git a/build/screenshots/dark/new_transfer_transaction.png b/build/screenshots/dark/new_transfer_transaction.png index 3bbf17017..6da41fa11 100644 Binary files a/build/screenshots/dark/new_transfer_transaction.png and b/build/screenshots/dark/new_transfer_transaction.png differ diff --git a/build/screenshots/dark/reports.png b/build/screenshots/dark/reports.png index 7bd939708..42a6db44c 100644 Binary files a/build/screenshots/dark/reports.png and b/build/screenshots/dark/reports.png differ diff --git a/build/screenshots/dark/search.png b/build/screenshots/dark/search.png index c6583ec20..58ed42492 100644 Binary files a/build/screenshots/dark/search.png and b/build/screenshots/dark/search.png differ diff --git a/build/screenshots/dark/settings_1.png b/build/screenshots/dark/settings_1.png index 628041276..c3a0e994e 100644 Binary files a/build/screenshots/dark/settings_1.png and b/build/screenshots/dark/settings_1.png differ diff --git a/build/screenshots/dark/settings_2.png b/build/screenshots/dark/settings_2.png index 0cbb5b675..07745fcf1 100644 Binary files a/build/screenshots/dark/settings_2.png and b/build/screenshots/dark/settings_2.png differ diff --git a/build/screenshots/dark/templates_1.png b/build/screenshots/dark/templates_1.png index ee985aade..a0074e2b1 100644 Binary files a/build/screenshots/dark/templates_1.png and b/build/screenshots/dark/templates_1.png differ diff --git a/build/screenshots/dark/templates_2.png b/build/screenshots/dark/templates_2.png deleted file mode 100644 index 8f0b2d521..000000000 Binary files a/build/screenshots/dark/templates_2.png and /dev/null differ diff --git a/build/screenshots/dark/transactions.png b/build/screenshots/dark/transactions.png index 1bddd35bf..a404e4064 100644 Binary files a/build/screenshots/dark/transactions.png and b/build/screenshots/dark/transactions.png differ diff --git a/build/screenshots/light/accounts.png b/build/screenshots/light/accounts.png index b2ec96479..ba4183b6f 100644 Binary files a/build/screenshots/light/accounts.png and b/build/screenshots/light/accounts.png differ diff --git a/build/screenshots/light/categories.png b/build/screenshots/light/categories.png index 54d7ba010..94a36483d 100644 Binary files a/build/screenshots/light/categories.png and b/build/screenshots/light/categories.png differ diff --git a/build/screenshots/light/chart_1.png b/build/screenshots/light/chart_1.png index fc360eca5..1dff6f9f1 100644 Binary files a/build/screenshots/light/chart_1.png and b/build/screenshots/light/chart_1.png differ diff --git a/build/screenshots/light/chart_2.png b/build/screenshots/light/chart_2.png index a12d8ab09..c6cb8ab78 100644 Binary files a/build/screenshots/light/chart_2.png and b/build/screenshots/light/chart_2.png differ diff --git a/build/screenshots/light/chart_3.png b/build/screenshots/light/chart_3.png index ce95eca27..a2133e80c 100644 Binary files a/build/screenshots/light/chart_3.png and b/build/screenshots/light/chart_3.png differ diff --git a/build/screenshots/light/chart_4.png b/build/screenshots/light/chart_4.png index c3ecd69d2..b440c9c6f 100644 Binary files a/build/screenshots/light/chart_4.png and b/build/screenshots/light/chart_4.png differ diff --git a/build/screenshots/light/filter_1.png b/build/screenshots/light/filter_1.png index 1d0cc77e8..2dee371ac 100644 Binary files a/build/screenshots/light/filter_1.png and b/build/screenshots/light/filter_1.png differ diff --git a/build/screenshots/light/filter_2.png b/build/screenshots/light/filter_2.png index 87548a7ed..eeea2d8da 100644 Binary files a/build/screenshots/light/filter_2.png and b/build/screenshots/light/filter_2.png differ diff --git a/build/screenshots/light/home.png b/build/screenshots/light/home.png index 4d644578d..9580d5955 100644 Binary files a/build/screenshots/light/home.png and b/build/screenshots/light/home.png differ diff --git a/build/screenshots/light/hotkeys.png b/build/screenshots/light/hotkeys.png index 48b1724cf..48c5985ac 100644 Binary files a/build/screenshots/light/hotkeys.png and b/build/screenshots/light/hotkeys.png differ diff --git a/build/screenshots/light/new_category.png b/build/screenshots/light/new_category.png index 7f073b2fd..1d26edf20 100644 Binary files a/build/screenshots/light/new_category.png and b/build/screenshots/light/new_category.png differ diff --git a/build/screenshots/light/new_normal_transaction.png b/build/screenshots/light/new_normal_transaction.png index 66edaa908..ba49581ee 100644 Binary files a/build/screenshots/light/new_normal_transaction.png and b/build/screenshots/light/new_normal_transaction.png differ diff --git a/build/screenshots/light/new_transaction_1.png b/build/screenshots/light/new_transaction_1.png index 26889c76f..13940d6a3 100644 Binary files a/build/screenshots/light/new_transaction_1.png and b/build/screenshots/light/new_transaction_1.png differ diff --git a/build/screenshots/light/new_transaction_2.png b/build/screenshots/light/new_transaction_2.png index 14a434d8c..39c45b824 100644 Binary files a/build/screenshots/light/new_transaction_2.png and b/build/screenshots/light/new_transaction_2.png differ diff --git a/build/screenshots/light/new_transfer_transaction.png b/build/screenshots/light/new_transfer_transaction.png index 2eec50146..b54cf43ee 100644 Binary files a/build/screenshots/light/new_transfer_transaction.png and b/build/screenshots/light/new_transfer_transaction.png differ diff --git a/build/screenshots/light/reports.png b/build/screenshots/light/reports.png index 23273e986..83f10b217 100644 Binary files a/build/screenshots/light/reports.png and b/build/screenshots/light/reports.png differ diff --git a/build/screenshots/light/search.png b/build/screenshots/light/search.png index 6ebc590b1..5ee707c97 100644 Binary files a/build/screenshots/light/search.png and b/build/screenshots/light/search.png differ diff --git a/build/screenshots/light/settings_1.png b/build/screenshots/light/settings_1.png index cd1c2a3fd..ee2d86f10 100644 Binary files a/build/screenshots/light/settings_1.png and b/build/screenshots/light/settings_1.png differ diff --git a/build/screenshots/light/settings_2.png b/build/screenshots/light/settings_2.png index 232af88e4..a55ea76d7 100644 Binary files a/build/screenshots/light/settings_2.png and b/build/screenshots/light/settings_2.png differ diff --git a/build/screenshots/light/templates_1.png b/build/screenshots/light/templates_1.png index a25ea3342..c2211e290 100644 Binary files a/build/screenshots/light/templates_1.png and b/build/screenshots/light/templates_1.png differ diff --git a/build/screenshots/light/templates_2.png b/build/screenshots/light/templates_2.png deleted file mode 100644 index e7ccbada1..000000000 Binary files a/build/screenshots/light/templates_2.png and /dev/null differ diff --git a/build/screenshots/light/transactions.png b/build/screenshots/light/transactions.png index ca39e3a06..facf1bd4a 100644 Binary files a/build/screenshots/light/transactions.png and b/build/screenshots/light/transactions.png differ diff --git a/pom.xml b/pom.xml index 9f82d2adf..fa143cb5b 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ de.deadlocker8 BudgetMaster - 2.4.5 + 2.5.0 BudgetMaster @@ -35,7 +35,7 @@ org.springframework.boot spring-boot-starter-parent - 2.2.5.RELEASE + 2.2.11.RELEASE @@ -54,23 +54,23 @@ UTF-8 UTF-8 - 1.8 + 11 - 2.0.6 - 1.2.1 - 0.39 - 3.4.1 + 3.2.0 + 3.0.1 + 0.40 + 3.5.1 1.0.0 - 5.12.0 - 1.8.3 - 1.6.1 + 5.15.1 + 1.10.2 + 1.6.5 5.50.0 3.141.59 - 3.15.0 + 3.17.1 ${maven.build.timestamp} dd.MM.yy - 28 + 29 Robert Goldmann build/${project.version} @@ -245,7 +245,7 @@ org.apache.maven.plugins maven-war-plugin - 3.2.2 + 3.3.1 ${basedir}/src/main ${project.outputDirectory} @@ -264,7 +264,7 @@ com.akathist.maven.plugins.launch4j launch4j-maven-plugin - 1.7.21 + 1.7.25 l4j-clui @@ -283,7 +283,7 @@ false false - 1.8.0 + 11 preferJre 64/32 @@ -296,7 +296,7 @@ org.apache.maven.plugins maven-surefire-plugin - 2.12 + 2.22.1 junit:junit -Dfile.encoding=UTF-8 @@ -311,7 +311,7 @@ org.codehaus.mojo build-helper-maven-plugin - 1.10 + 1.12 attach-artifacts diff --git a/src/main/java/de/deadlocker8/budgetmaster/Main.java b/src/main/java/de/deadlocker8/budgetmaster/Main.java index 1ec0c1ab7..77401e212 100644 --- a/src/main/java/de/deadlocker8/budgetmaster/Main.java +++ b/src/main/java/de/deadlocker8/budgetmaster/Main.java @@ -20,6 +20,7 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.StandardCopyOption; +import java.text.MessageFormat; import java.util.*; @@ -47,9 +48,9 @@ public Locale getLocale() } @Override - public String getBaseResource() + public String[] getBaseResources() { - return "languages/"; + return new String[]{"languages/base", "languages/news"}; } @Override @@ -57,10 +58,17 @@ public LocalizationMessageFormatter messageFormatter() { return new JavaMessageFormatter(); } + + @Override + public boolean useMultipleResourceBundles() + { + return true; + } }); Localization.load(); ProgramArgs.setArgs(Arrays.asList(args)); + LOGGER.debug(MessageFormat.format("Starting with ProgramArgs: {0}", ProgramArgs.getArgs())); Path applicationSupportFolder = getApplicationSupportFolder(); PathUtils.createDirectoriesIfNotExists(applicationSupportFolder); @@ -106,12 +114,12 @@ else if(ProgramArgs.isDebug()) } else { - LOGGER.error("Ignoring option --customFolder: provided path '" + customFolder.toString() + "' is not absolute"); + LOGGER.error(MessageFormat.format("Ignoring option --customFolder: provided path ''{0}'' is not absolute", customFolder.toString())); } } savePath = determineFolder(savePath); - LOGGER.info("Used save path: " + savePath.toString()); + LOGGER.info(MessageFormat.format("Used save path: {0}", savePath.toString())); return savePath; } @@ -180,6 +188,6 @@ public void run(ApplicationArguments args) private static void logAppInfo(String appName, String versionName, String versionCode, String versionDate) { - LOGGER.info(appName + " - v" + versionName + " - (versioncode: " + versionCode + ") from " + versionDate + ")"); + LOGGER.info(MessageFormat.format("{0} - v{1} - (versioncode: {2}) from {3})", appName, versionName, versionCode, versionDate)); } } \ No newline at end of file diff --git a/src/main/java/de/deadlocker8/budgetmaster/ProgramArgs.java b/src/main/java/de/deadlocker8/budgetmaster/ProgramArgs.java index 3a17d593c..f36195409 100644 --- a/src/main/java/de/deadlocker8/budgetmaster/ProgramArgs.java +++ b/src/main/java/de/deadlocker8/budgetmaster/ProgramArgs.java @@ -13,6 +13,10 @@ public class ProgramArgs private static List args = new ArrayList<>(); + private ProgramArgs() + { + } + public static void setArgs(List args) { ProgramArgs.args = args; diff --git a/src/main/java/de/deadlocker8/budgetmaster/accounts/Account.java b/src/main/java/de/deadlocker8/budgetmaster/accounts/Account.java index 0a3fdb525..fbbaa93ce 100644 --- a/src/main/java/de/deadlocker8/budgetmaster/accounts/Account.java +++ b/src/main/java/de/deadlocker8/budgetmaster/accounts/Account.java @@ -28,6 +28,7 @@ public class Account private Boolean isSelected = false; private Boolean isDefault = false; + private Boolean isReadOnly = false; @Expose private AccountType type; @@ -39,6 +40,7 @@ public Account(String name, AccountType type) this.type = type; this.isSelected = false; this.isDefault = false; + this.isReadOnly = false; } public Account() @@ -95,6 +97,16 @@ public void setDefault(Boolean aDefault) isDefault = aDefault; } + public Boolean isReadOnly() + { + return isReadOnly; + } + + public void setReadOnly(Boolean readOnly) + { + isReadOnly = readOnly; + } + public AccountType getType() { return type; @@ -114,6 +126,7 @@ public String toString() ", referringTransactions=" + referringTransactions + ", isSelected=" + isSelected + ", isDefault=" + isDefault + + ", isReadOnly=" + isReadOnly + ", type=" + type + '}'; } @@ -126,6 +139,7 @@ public boolean equals(Object o) Account account = (Account) o; return isSelected == account.isSelected && isDefault == account.isDefault && + isReadOnly == account.isReadOnly && Objects.equals(ID, account.ID) && Objects.equals(name, account.name) && type == account.type; @@ -134,6 +148,6 @@ public boolean equals(Object o) @Override public int hashCode() { - return Objects.hash(ID, name, isSelected, isDefault, type); + return Objects.hash(ID, name, isSelected, isDefault, isReadOnly, type); } } \ No newline at end of file diff --git a/src/main/java/de/deadlocker8/budgetmaster/accounts/AccountController.java b/src/main/java/de/deadlocker8/budgetmaster/accounts/AccountController.java index df03dddd7..54cd57729 100644 --- a/src/main/java/de/deadlocker8/budgetmaster/accounts/AccountController.java +++ b/src/main/java/de/deadlocker8/budgetmaster/accounts/AccountController.java @@ -2,37 +2,34 @@ import de.deadlocker8.budgetmaster.controller.BaseController; import de.deadlocker8.budgetmaster.settings.SettingsService; +import de.deadlocker8.budgetmaster.utils.Mappings; import de.deadlocker8.budgetmaster.utils.ResourceNotFoundException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.validation.BindingResult; import org.springframework.validation.FieldError; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.ModelAttribute; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.*; import javax.servlet.http.HttpServletRequest; import java.util.Optional; @Controller +@RequestMapping(Mappings.ACCOUNTS) public class AccountController extends BaseController { - private final AccountRepository accountRepository; private final AccountService accountService; private final SettingsService settingsService; @Autowired - public AccountController(AccountRepository accountRepository, AccountService accountService, SettingsService settingsService) + public AccountController(AccountService accountService, SettingsService settingsService) { - this.accountRepository = accountRepository; this.accountService = accountService; this.settingsService = settingsService; } - @GetMapping(value = "/accounts/{ID}/select") + @GetMapping(value = "/{ID}/select") public String selectAccount(HttpServletRequest request, @PathVariable("ID") Integer ID) { accountService.selectAccount(ID); @@ -45,7 +42,7 @@ public String selectAccount(HttpServletRequest request, @PathVariable("ID") Inte return "redirect:" + referer; } - @GetMapping(value = "/accounts/{ID}/setAsDefault") + @GetMapping(value = "/{ID}/setAsDefault") public String setAsDefault(HttpServletRequest request, @PathVariable("ID") Integer ID) { accountService.setAsDefaultAccount(ID); @@ -58,7 +55,31 @@ public String setAsDefault(HttpServletRequest request, @PathVariable("ID") Integ return "redirect:" + referer; } - @GetMapping("/accounts") + @GetMapping(value = "/{ID}/toggleReadOnly") + public String toggleReadOnly(HttpServletRequest request, @PathVariable("ID") Integer ID) + { + final Optional accountOptional = accountService.getRepository().findById(ID); + if(accountOptional.isEmpty()) + { + throw new ResourceNotFoundException(); + } + + final Account account = accountOptional.get(); + if(!account.isDefault()) + { + account.setReadOnly(!account.isReadOnly()); + accountService.getRepository().save(account); + } + + String referer = request.getHeader("Referer"); + if(referer.contains("database/import")) + { + return "redirect:/settings"; + } + return "redirect:" + referer; + } + + @GetMapping public String accounts(Model model) { model.addAttribute("accounts", accountService.getAllAccountsAsc()); @@ -66,32 +87,32 @@ public String accounts(Model model) return "accounts/accounts"; } - @GetMapping("/accounts/{ID}/requestDelete") + @GetMapping("/{ID}/requestDelete") public String requestDeleteAccount(Model model, @PathVariable("ID") Integer ID) { model.addAttribute("accounts", accountService.getAllAccountsAsc()); - model.addAttribute("currentAccount", accountRepository.getOne(ID)); + model.addAttribute("currentAccount", accountService.getRepository().getOne(ID)); model.addAttribute("settings", settingsService.getSettings()); return "accounts/accounts"; } - @GetMapping("/accounts/{ID}/delete") + @GetMapping("/{ID}/delete") public String deleteAccountAndReferringTransactions(Model model, @PathVariable("ID") Integer ID) { - if(accountRepository.findAllByType(AccountType.CUSTOM).size() > 1) + if(accountService.getRepository().findAllByType(AccountType.CUSTOM).size() > 1) { accountService.deleteAccount(ID); return "redirect:/accounts"; } model.addAttribute("accounts", accountService.getAllAccountsAsc()); - model.addAttribute("currentAccount", accountRepository.getOne(ID)); + model.addAttribute("currentAccount", accountService.getRepository().getOne(ID)); model.addAttribute("accountNotDeletable", true); model.addAttribute("settings", settingsService.getSettings()); return "accounts/accounts"; } - @GetMapping("/accounts/newAccount") + @GetMapping("/newAccount") public String newAccount(Model model) { Account emptyAccount = new Account(); @@ -100,11 +121,11 @@ public String newAccount(Model model) return "accounts/newAccount"; } - @GetMapping("/accounts/{ID}/edit") + @GetMapping("/{ID}/edit") public String editAccount(Model model, @PathVariable("ID") Integer ID) { - Optional accountOptional = accountRepository.findById(ID); - if(!accountOptional.isPresent()) + Optional accountOptional = accountService.getRepository().findById(ID); + if(accountOptional.isEmpty()) { throw new ResourceNotFoundException(); } @@ -114,7 +135,7 @@ public String editAccount(Model model, @PathVariable("ID") Integer ID) return "accounts/newAccount"; } - @PostMapping(value = "/accounts/newAccount") + @PostMapping(value = "/newAccount") public String post(HttpServletRequest request, Model model, @ModelAttribute("NewAccount") Account account, BindingResult bindingResult) @@ -122,7 +143,7 @@ public String post(HttpServletRequest request, Model model, AccountValidator accountValidator = new AccountValidator(); accountValidator.validate(account, bindingResult); - if(accountRepository.findByName(account.getName()) != null) + if(accountService.getRepository().findByName(account.getName()) != null) { bindingResult.addError(new FieldError("NewAccount", "name", "", false, new String[]{"warning.duplicate.account.name"}, null, null)); } @@ -140,17 +161,17 @@ public String post(HttpServletRequest request, Model model, if(account.getID() == null) { // new account - accountRepository.save(account); + accountService.getRepository().save(account); } else { // edit existing account - Optional existingAccountOptional = accountRepository.findById(account.getID()); + Optional existingAccountOptional = accountService.getRepository().findById(account.getID()); if(existingAccountOptional.isPresent()) { Account existingAccount = existingAccountOptional.get(); existingAccount.setName(account.getName()); - accountRepository.save(existingAccount); + accountService.getRepository().save(existingAccount); } } } diff --git a/src/main/java/de/deadlocker8/budgetmaster/accounts/AccountRepository.java b/src/main/java/de/deadlocker8/budgetmaster/accounts/AccountRepository.java index 01e72be7f..3536d8fb2 100644 --- a/src/main/java/de/deadlocker8/budgetmaster/accounts/AccountRepository.java +++ b/src/main/java/de/deadlocker8/budgetmaster/accounts/AccountRepository.java @@ -9,6 +9,8 @@ public interface AccountRepository extends JpaRepository { List findAllByTypeOrderByNameAsc(AccountType accountType); + List findAllByTypeAndIsReadOnlyOrderByNameAsc(AccountType accountType, Boolean isReadOnly); + Account findByName(String name); List findAllByType(AccountType accountType); diff --git a/src/main/java/de/deadlocker8/budgetmaster/accounts/AccountService.java b/src/main/java/de/deadlocker8/budgetmaster/accounts/AccountService.java index 9f0c7c522..a2c710af8 100644 --- a/src/main/java/de/deadlocker8/budgetmaster/accounts/AccountService.java +++ b/src/main/java/de/deadlocker8/budgetmaster/accounts/AccountService.java @@ -18,10 +18,11 @@ @Service public class AccountService implements Resetable { - private final Logger LOGGER = LoggerFactory.getLogger(this.getClass()); - private AccountRepository accountRepository; - private TransactionService transactionService; - private UserRepository userRepository; + private static final Logger LOGGER = LoggerFactory.getLogger(AccountService.class); + + private final AccountRepository accountRepository; + private final TransactionService transactionService; + private final UserRepository userRepository; @Autowired public AccountService(AccountRepository accountRepository, TransactionService transactionService, UserRepository userRepository) @@ -45,10 +46,17 @@ public List getAllAccountsAsc() return accounts; } + public List getAllActivatedAccountsAsc() + { + List accounts = accountRepository.findAllByType(AccountType.ALL); + accounts.addAll(accountRepository.findAllByTypeAndIsReadOnlyOrderByNameAsc(AccountType.CUSTOM, false)); + return accounts; + } + public void deleteAccount(int ID) { Optional accountToDeleteOptional = accountRepository.findById(ID); - if(!accountToDeleteOptional.isPresent()) + if(accountToDeleteOptional.isEmpty()) { return; } @@ -97,6 +105,16 @@ public void createDefaults() LOGGER.debug("Created default account"); } + // handle null values for new field "isReadOnly" + for(Account account : accountRepository.findAll()) + { + if(account.isReadOnly() == null) + { + account.setReadOnly(false); + } + accountRepository.save(account); + } + Account defaultAccount = accountRepository.findByIsDefault(true); if(defaultAccount == null) { @@ -121,7 +139,7 @@ public void selectAccount(int ID) deselectAllAccounts(); Optional accountToSelectOptional = accountRepository.findById(ID); - if(!accountToSelectOptional.isPresent()) + if(accountToSelectOptional.isEmpty()) { return; } @@ -140,15 +158,20 @@ public void selectAccount(int ID) public void setAsDefaultAccount(int ID) { - unsetDefaultForAllAccounts(); - Optional accountToSelectOptional = accountRepository.findById(ID); - if(!accountToSelectOptional.isPresent()) + if(accountToSelectOptional.isEmpty()) { return; } Account accountToSelect = accountToSelectOptional.get(); + if(accountToSelect.isReadOnly()) + { + return; + } + + unsetDefaultForAllAccounts(); + accountToSelect.setDefault(true); accountRepository.save(accountToSelect); } diff --git a/src/main/java/de/deadlocker8/budgetmaster/authentication/LoginController.java b/src/main/java/de/deadlocker8/budgetmaster/authentication/LoginController.java index c17be2d87..a6b133113 100644 --- a/src/main/java/de/deadlocker8/budgetmaster/authentication/LoginController.java +++ b/src/main/java/de/deadlocker8/budgetmaster/authentication/LoginController.java @@ -1,6 +1,7 @@ package de.deadlocker8.budgetmaster.authentication; import de.deadlocker8.budgetmaster.controller.BaseController; +import de.deadlocker8.budgetmaster.utils.Mappings; import org.joda.time.DateTime; import org.springframework.security.web.savedrequest.DefaultSavedRequest; import org.springframework.stereotype.Controller; @@ -12,20 +13,25 @@ import java.util.Map; @Controller +@RequestMapping(Mappings.LOGIN) public class LoginController extends BaseController { - @GetMapping("/login") + @GetMapping public String login(HttpServletRequest request, Model model) { Map paramMap = request.getParameterMap(); if(paramMap.containsKey("error")) + { model.addAttribute("isError", true); + } if(paramMap.containsKey("logout")) + { model.addAttribute("isLogout", true); + } - DefaultSavedRequest savedRequest = (DefaultSavedRequest)request.getSession().getAttribute("SPRING_SECURITY_SAVED_REQUEST"); + DefaultSavedRequest savedRequest = (DefaultSavedRequest) request.getSession().getAttribute("SPRING_SECURITY_SAVED_REQUEST"); if(savedRequest != null) { request.getSession().setAttribute("preLoginURL", savedRequest.getRequestURL()); diff --git a/src/main/java/de/deadlocker8/budgetmaster/authentication/UserService.java b/src/main/java/de/deadlocker8/budgetmaster/authentication/UserService.java index 702d3b1ca..8dc3b2cda 100644 --- a/src/main/java/de/deadlocker8/budgetmaster/authentication/UserService.java +++ b/src/main/java/de/deadlocker8/budgetmaster/authentication/UserService.java @@ -11,7 +11,8 @@ @Service public class UserService { - private final Logger LOGGER = LoggerFactory.getLogger(this.getClass()); + private static final Logger LOGGER = LoggerFactory.getLogger(UserService.class); + public static final String DEFAULT_PASSWORD = "BudgetMaster"; @Autowired diff --git a/src/main/java/de/deadlocker8/budgetmaster/categories/CategoryController.java b/src/main/java/de/deadlocker8/budgetmaster/categories/CategoryController.java index d824fbaed..93def3e53 100644 --- a/src/main/java/de/deadlocker8/budgetmaster/categories/CategoryController.java +++ b/src/main/java/de/deadlocker8/budgetmaster/categories/CategoryController.java @@ -4,6 +4,7 @@ import de.deadlocker8.budgetmaster.services.HelpersService; import de.deadlocker8.budgetmaster.settings.SettingsService; import de.deadlocker8.budgetmaster.utils.Colors; +import de.deadlocker8.budgetmaster.utils.Mappings; import de.deadlocker8.budgetmaster.utils.ResourceNotFoundException; import de.thecodelabs.utils.util.ColorUtilsNonJavaFX; import org.springframework.beans.factory.annotation.Autowired; @@ -18,6 +19,7 @@ @Controller +@RequestMapping(Mappings.CATEGORIES) public class CategoryController extends BaseController { private static final String WHITE = "#FFFFFF"; @@ -34,7 +36,7 @@ public CategoryController(CategoryService categoryService, HelpersService helper this.settingsService = settingsService; } - @GetMapping("/categories") + @GetMapping public String categories(Model model) { model.addAttribute("categories", categoryService.getAllCategories()); @@ -42,7 +44,7 @@ public String categories(Model model) return "categories/categories"; } - @GetMapping("/categories/{ID}/requestDelete") + @GetMapping("/{ID}/requestDelete") public String requestDeleteCategory(Model model, @PathVariable("ID") Integer ID) { if(!categoryService.isDeletable(ID)) @@ -55,15 +57,15 @@ public String requestDeleteCategory(Model model, @PathVariable("ID") Integer ID) model.addAttribute("categories", allCategories); model.addAttribute("availableCategories", availableCategories); - model.addAttribute("preselectedCategory", categoryService.getRepository().findByType(CategoryType.NONE)); + model.addAttribute("preselectedCategory", categoryService.findByType(CategoryType.NONE)); - model.addAttribute("currentCategory", categoryService.getRepository().getOne(ID)); + model.addAttribute("currentCategory", categoryService.findById(ID).get()); model.addAttribute("settings", settingsService.getSettings()); return "categories/categories"; } - @PostMapping(value = "/categories/{ID}/delete") - public String deleteCategory(Model model, @PathVariable("ID") Integer ID, @ModelAttribute("DestinationCategory") DestinationCategory destinationCategory) + @PostMapping(value = "/{ID}/delete") + public String deleteCategory(@PathVariable("ID") Integer ID, @ModelAttribute("DestinationCategory") DestinationCategory destinationCategory) { if(categoryService.isDeletable(ID)) { @@ -73,7 +75,7 @@ public String deleteCategory(Model model, @PathVariable("ID") Integer ID, @Model return "redirect:/categories"; } - @GetMapping("/categories/newCategory") + @GetMapping("/newCategory") public String newCategory(Model model) { //add custom color (defaults to white here because we are adding a new category instead of editing an existing) @@ -84,11 +86,11 @@ public String newCategory(Model model) return "categories/newCategory"; } - @GetMapping("/categories/{ID}/edit") + @GetMapping("/{ID}/edit") public String editCategory(Model model, @PathVariable("ID") Integer ID) { - Optional categoryOptional = categoryService.getRepository().findById(ID); - if(!categoryOptional.isPresent()) + Optional categoryOptional = categoryService.findById(ID); + if(categoryOptional.isEmpty()) { throw new ResourceNotFoundException(); } @@ -109,7 +111,7 @@ public String editCategory(Model model, @PathVariable("ID") Integer ID) return "categories/newCategory"; } - @PostMapping(value = "/categories/newCategory") + @PostMapping(value = "/newCategory") public String post(Model model, @ModelAttribute("NewCategory") Category category, BindingResult bindingResult) { CategoryValidator userValidator = new CategoryValidator(); @@ -142,7 +144,7 @@ public String post(Model model, @ModelAttribute("NewCategory") Category category { category.setType(CategoryType.CUSTOM); } - categoryService.getRepository().save(category); + categoryService.save(category); } return "redirect:/categories"; diff --git a/src/main/java/de/deadlocker8/budgetmaster/categories/CategoryService.java b/src/main/java/de/deadlocker8/budgetmaster/categories/CategoryService.java index 285a6743f..34c2ec81c 100644 --- a/src/main/java/de/deadlocker8/budgetmaster/categories/CategoryService.java +++ b/src/main/java/de/deadlocker8/budgetmaster/categories/CategoryService.java @@ -1,7 +1,6 @@ package de.deadlocker8.budgetmaster.categories; import de.deadlocker8.budgetmaster.services.Resetable; -import de.deadlocker8.budgetmaster.settings.SettingsService; import de.deadlocker8.budgetmaster.transactions.Transaction; import de.deadlocker8.budgetmaster.utils.Strings; import de.thecodelabs.utils.util.Localization; @@ -10,15 +9,16 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import java.util.Comparator; import java.util.List; -import java.util.Locale; import java.util.Optional; +import java.util.stream.Collectors; @Service public class CategoryService implements Resetable { - private final Logger LOGGER = LoggerFactory.getLogger(this.getClass()); - private CategoryRepository categoryRepository; + private static final Logger LOGGER = LoggerFactory.getLogger(CategoryService.class); + private final CategoryRepository categoryRepository; @Autowired public CategoryService(CategoryRepository categoryRepository) @@ -28,15 +28,25 @@ public CategoryService(CategoryRepository categoryRepository) createDefaults(); } - public CategoryRepository getRepository() + public Optional findById(Integer ID) { - return categoryRepository; + return categoryRepository.findById(ID); + } + + public Category findByType(CategoryType type) + { + return categoryRepository.findByType(type); + } + + public Category save(Category category) + { + return categoryRepository.save(category); } public void deleteCategory(int ID, Category newCategory) { Optional categoryOptional = categoryRepository.findById(ID); - if(!categoryOptional.isPresent()) + if(categoryOptional.isEmpty()) { throw new RuntimeException("Can't delete non-existing category with ID: " + ID); } @@ -57,7 +67,7 @@ public void deleteCategory(int ID, Category newCategory) @SuppressWarnings("OptionalIsPresent") public boolean isDeletable(Integer ID) { - Optional categoryOptional = getRepository().findById(ID); + Optional categoryOptional = findById(ID); if(categoryOptional.isPresent()) { return categoryOptional.get().getType() == CategoryType.CUSTOM; @@ -91,7 +101,9 @@ public void createDefaults() public List getAllCategories() { localizeDefaultCategories(); - return categoryRepository.findAllByOrderByNameAsc(); + return categoryRepository.findAllByOrderByNameAsc().stream() + .sorted(Comparator.comparing(c -> c.getName().toLowerCase())) + .collect(Collectors.toList()); } public void localizeDefaultCategories() diff --git a/src/main/java/de/deadlocker8/budgetmaster/charts/ChartController.java b/src/main/java/de/deadlocker8/budgetmaster/charts/ChartController.java index 2cc0de75a..32dd04805 100644 --- a/src/main/java/de/deadlocker8/budgetmaster/charts/ChartController.java +++ b/src/main/java/de/deadlocker8/budgetmaster/charts/ChartController.java @@ -11,6 +11,7 @@ import de.deadlocker8.budgetmaster.settings.SettingsService; import de.deadlocker8.budgetmaster.transactions.Transaction; import de.deadlocker8.budgetmaster.transactions.TransactionService; +import de.deadlocker8.budgetmaster.utils.Mappings; import de.deadlocker8.budgetmaster.utils.ResourceNotFoundException; import org.joda.time.DateTime; import org.joda.time.format.ISODateTimeFormat; @@ -25,6 +26,7 @@ import java.util.UUID; @Controller +@RequestMapping(Mappings.CHARTS) public class ChartController extends BaseController { private static final Gson GSON = new GsonBuilder() @@ -49,7 +51,7 @@ public ChartController(ChartService chartService, HelpersService helpers, Settin this.transactionService = transactionService; } - @GetMapping("/charts") + @GetMapping public String charts(Model model) { List charts = chartService.getRepository().findAllByOrderByNameAsc(); @@ -66,12 +68,12 @@ public String charts(Model model) return "charts/charts"; } - @PostMapping(value = "/charts") + @PostMapping public String showChart(Model model, @ModelAttribute("NewChartSettings") ChartSettings chartSettings) { chartSettings.setFilterConfiguration(filterHelpersService.updateCategoriesAndTags(chartSettings.getFilterConfiguration())); Optional chartOptional = chartService.getRepository().findById(chartSettings.getChartID()); - if(!chartOptional.isPresent()) + if(chartOptional.isEmpty()) { throw new ResourceNotFoundException(); } @@ -88,7 +90,7 @@ public String showChart(Model model, @ModelAttribute("NewChartSettings") ChartSe return "charts/charts"; } - @GetMapping("/charts/manage") + @GetMapping("/manage") public String manage(Model model) { model.addAttribute("charts", chartService.getRepository().findAllByOrderByNameAsc()); @@ -96,7 +98,7 @@ public String manage(Model model) return "charts/manage"; } - @GetMapping("/charts/newChart") + @GetMapping("/newChart") public String newChart(Model model) { Chart emptyChart = DefaultCharts.CHART_DEFAULT; @@ -105,11 +107,11 @@ public String newChart(Model model) return "charts/newChart"; } - @GetMapping("/charts/{ID}/edit") + @GetMapping("/{ID}/edit") public String editChart(Model model, @PathVariable("ID") Integer ID) { Optional chartOptional = chartService.getRepository().findById(ID); - if(!chartOptional.isPresent()) + if(chartOptional.isEmpty()) { throw new ResourceNotFoundException(); } @@ -119,7 +121,7 @@ public String editChart(Model model, @PathVariable("ID") Integer ID) return "charts/newChart"; } - @PostMapping(value = "/charts/newChart") + @PostMapping(value = "/newChart") public String post(Model model, @ModelAttribute("NewChart") Chart chart, BindingResult bindingResult) { ChartValidator userValidator = new ChartValidator(); @@ -165,7 +167,7 @@ public String post(Model model, @ModelAttribute("NewChart") Chart chart, Binding return "redirect:/charts/manage"; } - @GetMapping("/charts/{ID}/requestDelete") + @GetMapping("/{ID}/requestDelete") public String requestDeleteChart(Model model, @PathVariable("ID") Integer ID) { if(!chartService.isDeletable(ID)) @@ -179,7 +181,7 @@ public String requestDeleteChart(Model model, @PathVariable("ID") Integer ID) return "charts/manage"; } - @GetMapping(value = "/charts/{ID}/delete") + @GetMapping(value = "/{ID}/delete") public String deleteChart(Model model, @PathVariable("ID") Integer ID) { if(chartService.isDeletable(ID)) diff --git a/src/main/java/de/deadlocker8/budgetmaster/charts/ChartService.java b/src/main/java/de/deadlocker8/budgetmaster/charts/ChartService.java index 8eefb4573..5b97ebc34 100644 --- a/src/main/java/de/deadlocker8/budgetmaster/charts/ChartService.java +++ b/src/main/java/de/deadlocker8/budgetmaster/charts/ChartService.java @@ -6,15 +6,17 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import java.text.MessageFormat; import java.util.List; import java.util.Optional; @Service public class ChartService implements Resetable { - private final Logger LOGGER = LoggerFactory.getLogger(this.getClass()); - private final String PATTERN_OLD_CONTAINER_ID = "Plotly.newPlot('chart-canvas',"; - private final String PATTERN_DYNAMIC_CONTAINER_ID = "Plotly.newPlot('containerID',"; + private static final Logger LOGGER = LoggerFactory.getLogger(ChartService.class); + + private static final String PATTERN_OLD_CONTAINER_ID = "Plotly.newPlot('chart-canvas',"; + private static final String PATTERN_DYNAMIC_CONTAINER_ID = "Plotly.newPlot('containerID',"; private ChartRepository chartRepository; @@ -60,11 +62,11 @@ public void createDefaults() { chart.setID(defaultCharts.indexOf(chart) + 1); chartRepository.save(chart); - LOGGER.debug("Created default chart '" + chart.getName() + "'"); + LOGGER.debug(MessageFormat.format("Created default chart ''{0}''", chart.getName())); } else if(currentChart.getVersion() < chart.getVersion()) { - LOGGER.debug("Update default chart '" + chart.getName() + "' from version " + currentChart.getVersion() + " to " + chart.getVersion()); + LOGGER.debug(MessageFormat.format("Update default chart ''{0}'' from version {1} to {2}", chart.getName(), currentChart.getVersion(), chart.getVersion())); currentChart.setVersion(chart.getVersion()); currentChart.setScript(chart.getScript()); chartRepository.save(currentChart); @@ -91,7 +93,7 @@ private void updateUserCharts() String script = userChart.getScript(); if(script.contains(PATTERN_OLD_CONTAINER_ID)) { - LOGGER.debug("Updating user chart '" + userChart.getName() + "' with ID " + userChart.getID()); + LOGGER.debug(MessageFormat.format("Updating user chart ''{0}'' with ID {1}", userChart.getName(), userChart.getID())); script = script.replace(PATTERN_OLD_CONTAINER_ID, PATTERN_DYNAMIC_CONTAINER_ID); userChart.setScript(script); getRepository().save(userChart); diff --git a/src/main/java/de/deadlocker8/budgetmaster/charts/DefaultCharts.java b/src/main/java/de/deadlocker8/budgetmaster/charts/DefaultCharts.java index 8aaee465f..b27732158 100644 --- a/src/main/java/de/deadlocker8/budgetmaster/charts/DefaultCharts.java +++ b/src/main/java/de/deadlocker8/budgetmaster/charts/DefaultCharts.java @@ -6,6 +6,7 @@ import java.io.IOException; import java.net.URL; +import java.text.MessageFormat; import java.util.ArrayList; import java.util.List; @@ -72,7 +73,7 @@ private static String getChartFromFile(String filePath) URL url = DefaultCharts.class.getClassLoader().getResource(filePath); if(url == null) { - LOGGER.warn("Couldn't add default chart '" + filePath + "' due to missing file"); + LOGGER.warn(MessageFormat.format("Couldn''t add default chart ''{0}'' due to missing file", filePath)); return ""; } diff --git a/src/main/java/de/deadlocker8/budgetmaster/controller/AboutController.java b/src/main/java/de/deadlocker8/budgetmaster/controller/AboutController.java index b4f6c695c..de1ed1204 100644 --- a/src/main/java/de/deadlocker8/budgetmaster/controller/AboutController.java +++ b/src/main/java/de/deadlocker8/budgetmaster/controller/AboutController.java @@ -1,13 +1,21 @@ package de.deadlocker8.budgetmaster.controller; import de.deadlocker8.budgetmaster.settings.SettingsService; +import de.deadlocker8.budgetmaster.utils.Mappings; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; +import org.springframework.transaction.annotation.Transactional; import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; +import javax.servlet.http.HttpServletRequest; +import java.util.ArrayList; +import java.util.List; + @Controller +@RequestMapping(Mappings.ABOUT) public class AboutController extends BaseController { private final SettingsService settingsService; @@ -18,10 +26,32 @@ public AboutController(SettingsService settingsService) this.settingsService = settingsService; } - @RequestMapping("/about") + @GetMapping public String index(Model model) { model.addAttribute("settings", settingsService.getSettings()); return "about"; } + + @GetMapping("/whatsNewModal") + public String whatsNewModal(Model model) + { + final List newsEntries = new ArrayList<>(); + newsEntries.add(NewsEntry.createWithLocalizationKeys("news.changeType.headline", "news.changeType.description")); + newsEntries.add(NewsEntry.createWithLocalizationKeys("news.readonlyAccounts.headline", "news.readonlyAccounts.description")); + newsEntries.add(NewsEntry.createWithLocalizationKeys("news.firstUseWizard.headline", "news.firstUseWizard.description")); + newsEntries.add(NewsEntry.createWithLocalizationKeys("news.java11.headline", "news.java11.description")); + + model.addAttribute("newsEntries", newsEntries); + return "whatsNewModal"; + } + + @RequestMapping("/whatsNewModal/close") + @Transactional + public String whatsNewModalClose(HttpServletRequest request, Model model) + { + settingsService.getSettings().setWhatsNewShownForCurrentVersion(true); + model.addAttribute("settings", settingsService.getSettings()); + return "redirect:" + request.getHeader("Referer"); + } } \ No newline at end of file diff --git a/src/main/java/de/deadlocker8/budgetmaster/controller/BackupController.java b/src/main/java/de/deadlocker8/budgetmaster/controller/BackupController.java index 234c2913e..843f116ec 100644 --- a/src/main/java/de/deadlocker8/budgetmaster/controller/BackupController.java +++ b/src/main/java/de/deadlocker8/budgetmaster/controller/BackupController.java @@ -1,6 +1,7 @@ package de.deadlocker8.budgetmaster.controller; import de.deadlocker8.budgetmaster.settings.SettingsService; +import de.deadlocker8.budgetmaster.utils.Mappings; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; @@ -10,6 +11,7 @@ @Controller +@RequestMapping(Mappings.BACKUP_REMINDER) public class BackupController extends BaseController { private final SettingsService settingsService; @@ -20,7 +22,7 @@ public BackupController(SettingsService settingsService) this.settingsService = settingsService; } - @RequestMapping("/backupReminder/cancel") + @RequestMapping("/cancel") public String cancel(HttpServletRequest request, Model model) { settingsService.updateLastBackupReminderDate(); @@ -28,7 +30,7 @@ public String cancel(HttpServletRequest request, Model model) return "redirect:" + request.getHeader("Referer"); } - @RequestMapping("/backupReminder/settings") + @RequestMapping("/settings") public String settings() { settingsService.updateLastBackupReminderDate(); diff --git a/src/main/java/de/deadlocker8/budgetmaster/controller/HotKeysController.java b/src/main/java/de/deadlocker8/budgetmaster/controller/HotKeysController.java index 95ae9cd60..38ad6198c 100644 --- a/src/main/java/de/deadlocker8/budgetmaster/controller/HotKeysController.java +++ b/src/main/java/de/deadlocker8/budgetmaster/controller/HotKeysController.java @@ -1,6 +1,7 @@ package de.deadlocker8.budgetmaster.controller; import de.deadlocker8.budgetmaster.settings.SettingsService; +import de.deadlocker8.budgetmaster.utils.Mappings; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; @@ -18,7 +19,7 @@ public HotKeysController(SettingsService settingsService) this.settingsService = settingsService; } - @RequestMapping("/hotkeys") + @RequestMapping(Mappings.HOTKEYS) public String index(Model model) { model.addAttribute("settings", settingsService.getSettings()); diff --git a/src/main/java/de/deadlocker8/budgetmaster/controller/IndexController.java b/src/main/java/de/deadlocker8/budgetmaster/controller/IndexController.java index 8b1423812..b2fe115a9 100644 --- a/src/main/java/de/deadlocker8/budgetmaster/controller/IndexController.java +++ b/src/main/java/de/deadlocker8/budgetmaster/controller/IndexController.java @@ -4,6 +4,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; @@ -18,10 +19,17 @@ public IndexController(SettingsService settingsService) this.settingsService = settingsService; } - @RequestMapping("/") + @RequestMapping public String index(Model model) { model.addAttribute("settings", settingsService.getSettings()); return "index"; } + + @GetMapping("/firstUse") + public String firstUse(Model model) + { + model.addAttribute("settings", settingsService.getSettings()); + return "firstUse"; + } } \ No newline at end of file diff --git a/src/main/java/de/deadlocker8/budgetmaster/controller/NewsEntry.java b/src/main/java/de/deadlocker8/budgetmaster/controller/NewsEntry.java new file mode 100644 index 000000000..c7ed16289 --- /dev/null +++ b/src/main/java/de/deadlocker8/budgetmaster/controller/NewsEntry.java @@ -0,0 +1,39 @@ +package de.deadlocker8.budgetmaster.controller; + +import de.thecodelabs.utils.util.Localization; + +public class NewsEntry +{ + private String headline; + private String description; + + public NewsEntry(String headline, String description) + { + this.headline = headline; + this.description = description; + } + + public static NewsEntry createWithLocalizationKeys(String headlineKey, String descriptionKey) + { + return new NewsEntry(Localization.getString(headlineKey), Localization.getString(descriptionKey)); + } + + public String getHeadline() + { + return headline; + } + + public String getDescription() + { + return description; + } + + @Override + public String toString() + { + return "NewsEntry{" + + "headline='" + headline + '\'' + + ", description='" + description + '\'' + + '}'; + } +} diff --git a/src/main/java/de/deadlocker8/budgetmaster/controller/TeapotController.java b/src/main/java/de/deadlocker8/budgetmaster/controller/TeapotController.java index d5fc0e947..a273dd5e9 100644 --- a/src/main/java/de/deadlocker8/budgetmaster/controller/TeapotController.java +++ b/src/main/java/de/deadlocker8/budgetmaster/controller/TeapotController.java @@ -1,5 +1,6 @@ package de.deadlocker8.budgetmaster.controller; +import de.deadlocker8.budgetmaster.utils.Mappings; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; @@ -7,7 +8,7 @@ @Controller public class TeapotController extends BaseController { - @RequestMapping("/418") + @RequestMapping(Mappings.TEAPOT) public String index() { return "error/418"; diff --git a/src/main/java/de/deadlocker8/budgetmaster/database/DatabaseParser.java b/src/main/java/de/deadlocker8/budgetmaster/database/DatabaseParser.java index e725f1f65..4094442a6 100644 --- a/src/main/java/de/deadlocker8/budgetmaster/database/DatabaseParser.java +++ b/src/main/java/de/deadlocker8/budgetmaster/database/DatabaseParser.java @@ -8,6 +8,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.text.MessageFormat; + public class DatabaseParser { final Logger LOGGER = LoggerFactory.getLogger(this.getClass()); @@ -32,26 +34,26 @@ public Database parseDatabaseFromJSON() throws IllegalArgumentException } int version = root.get("VERSION").getAsInt(); - LOGGER.info("Parsing BudgetMaster database with version " + version); + LOGGER.info(MessageFormat.format("Parsing BudgetMaster database with version {0}", version)); if(version == 2) { final Database database = new LegacyParser(jsonString, categoryNone).parseDatabaseFromJSON(); - LOGGER.debug("Parsed database with " + database.getTransactions().size() + " transactions, " + database.getCategories().size() + " categories and " + database.getAccounts().size() + " accounts"); + LOGGER.debug(MessageFormat.format("Parsed database with {0} transactions, {1} categories and {2} accounts", database.getTransactions().size(), database.getCategories().size(), database.getAccounts().size())); return database; } if(version == 3) { final Database database = new DatabaseParser_v3(jsonString).parseDatabaseFromJSON(); - LOGGER.debug("Parsed database with " + database.getTransactions().size() + " transactions, " + database.getCategories().size() + " categories and " + database.getAccounts().size() + " accounts"); + LOGGER.debug(MessageFormat.format("Parsed database with {0} transactions, {1} categories and {2} accounts", database.getTransactions().size(), database.getCategories().size(), database.getAccounts().size())); return database; } if(version == 4) { final Database database = new DatabaseParser_v4(jsonString).parseDatabaseFromJSON(); - LOGGER.debug("Parsed database with " + database.getTransactions().size() + " transactions, " + database.getCategories().size() + " categories and " + database.getAccounts().size() + " accounts and " + database.getTemplates().size() + " templates"); + LOGGER.debug(MessageFormat.format("Parsed database with {0} transactions, {1} categories, {2} accounts and {3} templates", database.getTransactions().size(), database.getCategories().size(), database.getAccounts().size(), database.getTemplates().size())); return database; } diff --git a/src/main/java/de/deadlocker8/budgetmaster/database/DatabaseParser_v3.java b/src/main/java/de/deadlocker8/budgetmaster/database/DatabaseParser_v3.java index dd52b1789..2e0a64fc1 100644 --- a/src/main/java/de/deadlocker8/budgetmaster/database/DatabaseParser_v3.java +++ b/src/main/java/de/deadlocker8/budgetmaster/database/DatabaseParser_v3.java @@ -105,14 +105,14 @@ protected List parseTransactions(JsonObject root) return parsedTransactions; } - private RepeatingOption parseRepeatingOption(JsonObject transactiob, DateTime startDate) + protected RepeatingOption parseRepeatingOption(JsonObject transaction, DateTime startDate) { - if(!transactiob.has("repeatingOption")) + if(!transaction.has("repeatingOption")) { return null; } - JsonObject option = transactiob.get("repeatingOption").getAsJsonObject(); + JsonObject option = transaction.get("repeatingOption").getAsJsonObject(); JsonObject repeatingModifier = option.get("modifier").getAsJsonObject(); String repeatingModifierType = repeatingModifier.get("localizationKey").getAsString(); diff --git a/src/main/java/de/deadlocker8/budgetmaster/database/DatabaseParser_v4.java b/src/main/java/de/deadlocker8/budgetmaster/database/DatabaseParser_v4.java index eeba1be6f..a80ebbec7 100644 --- a/src/main/java/de/deadlocker8/budgetmaster/database/DatabaseParser_v4.java +++ b/src/main/java/de/deadlocker8/budgetmaster/database/DatabaseParser_v4.java @@ -6,6 +6,9 @@ import com.google.gson.JsonParser; import de.deadlocker8.budgetmaster.templates.Template; import de.deadlocker8.budgetmaster.transactions.Transaction; +import de.deadlocker8.budgetmaster.transactions.TransactionBase; +import org.joda.time.DateTime; +import org.joda.time.format.DateTimeFormat; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -36,6 +39,53 @@ public Database parseDatabaseFromJSON() throws IllegalArgumentException return new Database(categories, accounts, transactions, templates); } + @Override + protected List parseTransactions(JsonObject root) + { + List parsedTransactions = new ArrayList<>(); + JsonArray transactions = root.get("transactions").getAsJsonArray(); + for(JsonElement currentTransaction : transactions) + { + final JsonObject transactionObject = currentTransaction.getAsJsonObject(); + + + int amount = transactionObject.get("amount").getAsInt(); + String name = transactionObject.get("name").getAsString(); + String description = transactionObject.get("description").getAsString(); + + Transaction transaction = new Transaction(); + transaction.setAmount(amount); + transaction.setName(name); + transaction.setDescription(description); + transaction.setTags(parseTags(transactionObject)); + + int categoryID = transactionObject.get("category").getAsJsonObject().get("ID").getAsInt(); + transaction.setCategory(getCategoryByID(categoryID)); + + int accountID = transactionObject.get("account").getAsJsonObject().get("ID").getAsInt(); + transaction.setAccount(getAccountByID(accountID)); + + JsonElement transferAccount = transactionObject.get("transferAccount"); + if(transferAccount != null) + { + int transferAccountID = transferAccount.getAsJsonObject().get("ID").getAsInt(); + transaction.setTransferAccount(getAccountByID(transferAccountID)); + } + + String date = transactionObject.get("date").getAsString(); + DateTime parsedDate = DateTime.parse(date, DateTimeFormat.forPattern("yyyy-MM-dd")); + transaction.setDate(parsedDate); + + transaction.setRepeatingOption(super.parseRepeatingOption(transactionObject, parsedDate)); + + handleIsExpenditure(transactionObject, transaction); + + parsedTransactions.add(transaction); + } + + return parsedTransactions; + } + protected List