### frontend: - Routes.js (path: 'xxx**Win**' for Window component) - load js resources: ```javascript // option 1 items: [ Ext.create('App.test.TestPanel',{ title: 'Title 1' }), { xtype: 'panel', title: 'Title 2' } ] // option 2 requires: ['App.xxxx.XxxxPanel'], items: [{ xtype: 'test.testPanel', title: 'Title 1' }] ``` - get component e.g. ```javascript me.down('test\\.testPanel') //alias: 'widget.test.testPanel' - i18n: static/public/i18n/{locale}.json ```javascript // component level bind: { text: '{i18n.xx.xx}' } // program level App.getI18n('xx.xx') // or App.getI18n().xx.xx - security - App.security.getUser() - App.security.isGranted(string | string[]) - App.security.isGrantedAll(string[]) ### backend (draft): - vs code dev: example.launch.json - db migration: resources/db/changelog/changes/\*\* (support subfolder) - suggested that one changeset with one DDL - filename: {yyyyMMdd}\_{number}\_{name}/{number}_{name}.sql - filename 2(optional): {yyyyMMdd}\_{number}\_{name}/{number}\_{name}/{number}\_{name}.sql ```sql --liquibase formatted sql --changeset {name}:{id} --comment: remarks (optional) CREATE TABLE `tableName` ( `id` INT PRIMARY KEY AUTO_INCREMENT, `created` DATETIME NOT NULL DEFAULT NOW(), `createdBy` VARCHAR(30), `version` INT NOT NULL DEFAULT 0, `modified` DATETIME NOT NULL DEFAULT NOW(), `modifiedBy` VARCHAR(30), `deleted` BOOLEAN NOT NULL DEFAULT FALSE, `column1` INT NOT NULL, `column2` VARCHAR(255) ); ``` - controller - AbstractController - api: /public/\*\* /protected/\*\* - RequestBody Class (`@NotNull`, `@NotBlank`, `@Min`, `@Max`, `@Pattern`, etc...) ```java @NotNull @Pattern(regexp = "^Y$|^N$") private String YesNo; ``` - authentication check: `@PreAuthorize` - common response class `DataRes`, `IdRes`, `RecrodsRes`, `SuccessRes` - RESTfull api (http method, http status) - http status: - 404: record not found - 409: record_line not found or record_line not under the record (relation check) - 422: something wrong, (e.g. qty not enough) - suggest /save api (also allowed for POST: new, PUT: update) 1. POST /xxxx/save 2. body: ```json { "id": 1, //update "xx": "xx", "updateLines": [ { "id": null, // new "xx": "xx" }, { // need a relation check "id": 1, //update "xx": "xx" } ], // need a relation check "deleteLineIds": [1, 2] } ``` ```java //example @RestController @RequestMapping("/xxxx") public class ExampleController extends AbstractController { @PostMapping('/xxx') // @GetMapping @DeleteMapping @PutMapping @PatchMapping //@ResponseStatus(HttpStatus.CREATED) //@PreAuthorize("hasAuthority('XXX_MAINT')") public Xxxx api (@RequestBody @Valid ReqClass req) { } @GetMapping('/{id}') public Xxxx getXxx (@PathVariable int id) { } } ``` - service - AbstractService, AbstractIdEntityService, AbstractBaseEntityService - `@Transactional(rollbackFor = Exception.class)` for write, read only not need transaction - dao - interface - AbstractDao - [Query Creation](https://docs.spring.io/spring-data/jpa/docs/2.7.0/reference/html/#jpa.query-methods.query-creation) - `@Query` ```java public Optional findByUsernameAndDeletedFalse(String username); // same @Query("FROM #{#entityName} u WHERE u.deleted = FALSE AND u.username = :username") public Optional findByUsername(@Param("username") String username); ``` - entity - IdEntity, BaseEntity - validation: `@NotNull`, `@NotBlank`, `@Min`, `@Max`, `@Pattern`, etc... - jdbcDao - reduce Map: queryForEntity, queryForList("SELECT xxx" , params, XxxRecord.class) - [Class Object vs Hashmap](https://stackoverflow.com/questions/10258071/class-object-vs-hashmap) - reduce `@Autowired` - use constructor - try separate class (e.g. FileListController, FileDownloadController) - return Optional instead of null - return Optional\ : may null - return T : must not null - i18n: i18n/messages_{locale(underscore)}.properties - `messageSource.getMessage("name", null, LocaleUtils.getLocale());` - reporting: - excel: - files: - excel/{filename}.xlsx - excel/{filename}_zh-TW.xlsx - excel/{filename}_zh-CN.xlsx - load Workbook: `ExcelUtils.loadTemplate("excel/{filename}");` - send out Workbook: `ExcelUtils.send(response, workbook, "filename"); // will output filename.xlsx` - jasper - files: - reports/{filename}.jrxml - reports/{filename}_zh-TW.jrxml - reports/{filename}_zh-CN.jrxml - compile: `JasperUtils.compile("reports/{filename}", params, new JRBeanCollectionDataSource(xxx), Map.of("subReport1","reports/{filename}"));` - send out: `JasperUtils.responsePdf(response, jRerpot, "filename"); // will output filename.pdf` - mobile: - login: POST /mobile/login - logout: POST /mobile/logout - call api: http headers with 1. `X-ATT-DeviceId` 2. `X-ATT-Access-Token`