Browse Source

feat: init api (#2)

master
vran 3 years ago
committed by GitHub
parent
commit
61e5708196
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 5
      api.gradle
  2. 10
      api/Dockerfile
  3. 41
      api/build.gradle
  4. 13
      api/src/main/java/com/databasir/DatabasirApplication.java
  5. 44
      api/src/main/java/com/databasir/api/DocumentController.java
  6. 106
      api/src/main/java/com/databasir/api/GroupController.java
  7. 55
      api/src/main/java/com/databasir/api/LoginController.java
  8. 58
      api/src/main/java/com/databasir/api/ProjectController.java
  9. 84
      api/src/main/java/com/databasir/api/Routes.java
  10. 35
      api/src/main/java/com/databasir/api/SettingController.java
  11. 105
      api/src/main/java/com/databasir/api/UserController.java
  12. 234
      api/src/main/java/com/databasir/api/advice/DatabasirExceptionAdvice.java
  13. 75
      api/src/main/java/com/databasir/api/config/SecurityConfig.java
  14. 18
      api/src/main/java/com/databasir/api/config/WebConfig.java
  15. 26
      api/src/main/java/com/databasir/api/config/security/DatabasirAuthenticationEntryPoint.java
  16. 49
      api/src/main/java/com/databasir/api/config/security/DatabasirAuthenticationFailureHandler.java
  17. 61
      api/src/main/java/com/databasir/api/config/security/DatabasirAuthenticationSuccessHandler.java
  18. 56
      api/src/main/java/com/databasir/api/config/security/DatabasirJwtTokenFilter.java
  19. 34
      api/src/main/java/com/databasir/api/config/security/DatabasirUserDetailService.java
  20. 67
      api/src/main/java/com/databasir/api/config/security/DatabasirUserDetails.java
  21. 27
      api/src/main/java/com/databasir/api/validator/UserOperationValidator.java
  22. 11
      api/src/main/resources/application-local.properties
  23. 12
      api/src/main/resources/application.properties
  24. 30
      build.gradle
  25. 3
      common/build.gradle
  26. 8
      common/src/main/java/com/databasir/common/DatabasirErrors.java
  27. 49
      common/src/main/java/com/databasir/common/DatabasirException.java
  28. 39
      common/src/main/java/com/databasir/common/JsonData.java
  29. 27
      common/src/main/java/com/databasir/common/SystemException.java
  30. 36
      common/src/main/java/com/databasir/common/codec/Aes.java
  31. 72
      common/src/main/java/com/databasir/common/codec/Rsa.java
  32. 18
      common/src/main/java/com/databasir/common/codec/Sha.java
  33. 4
      common/src/main/java/com/databasir/common/exception/Forbidden.java
  34. 24
      common/src/main/java/com/databasir/common/exception/InvalidTokenException.java
  35. 35
      common/src/test/java/com/databasir/common/codec/CodecTest.java
  36. 34
      core/build.gradle
  37. 38
      core/src/main/java/com/databasir/core/domain/DomainErrors.java
  38. 9
      core/src/main/java/com/databasir/core/domain/document/converter/BaseConverter.java
  39. 20
      core/src/main/java/com/databasir/core/domain/document/converter/DocumentHistoryPojoConverter.java
  40. 74
      core/src/main/java/com/databasir/core/domain/document/converter/DocumentPojoConverter.java
  41. 31
      core/src/main/java/com/databasir/core/domain/document/converter/DocumentResponseConverter.java
  42. 14
      core/src/main/java/com/databasir/core/domain/document/converter/NullToEmpty.java
  43. 125
      core/src/main/java/com/databasir/core/domain/document/data/DatabaseDocumentResponse.java
  44. 20
      core/src/main/java/com/databasir/core/domain/document/data/DatabaseDocumentVersionResponse.java
  45. 186
      core/src/main/java/com/databasir/core/domain/document/service/DocumentService.java
  46. 14
      core/src/main/java/com/databasir/core/domain/group/converter/GroupPojoConverter.java
  47. 39
      core/src/main/java/com/databasir/core/domain/group/converter/GroupResponseConverter.java
  48. 24
      core/src/main/java/com/databasir/core/domain/group/data/GroupCreateRequest.java
  49. 12
      core/src/main/java/com/databasir/core/domain/group/data/GroupMemberCreateRequest.java
  50. 32
      core/src/main/java/com/databasir/core/domain/group/data/GroupMemberPageCondition.java
  51. 21
      core/src/main/java/com/databasir/core/domain/group/data/GroupMemberPageResponse.java
  52. 14
      core/src/main/java/com/databasir/core/domain/group/data/GroupMemberRoleUpdateRequest.java
  53. 19
      core/src/main/java/com/databasir/core/domain/group/data/GroupPageCondition.java
  54. 20
      core/src/main/java/com/databasir/core/domain/group/data/GroupPageResponse.java
  55. 34
      core/src/main/java/com/databasir/core/domain/group/data/GroupResponse.java
  56. 26
      core/src/main/java/com/databasir/core/domain/group/data/GroupUpdateRequest.java
  57. 146
      core/src/main/java/com/databasir/core/domain/group/service/GroupService.java
  58. 13
      core/src/main/java/com/databasir/core/domain/login/data/AccessTokenRefreshRequest.java
  59. 15
      core/src/main/java/com/databasir/core/domain/login/data/AccessTokenRefreshResponse.java
  60. 31
      core/src/main/java/com/databasir/core/domain/login/data/LoginKeyResponse.java
  61. 88
      core/src/main/java/com/databasir/core/domain/login/service/LoginService.java
  62. 41
      core/src/main/java/com/databasir/core/domain/project/converter/DataSourcePojoConverter.java
  63. 34
      core/src/main/java/com/databasir/core/domain/project/converter/ProjectPojoConverter.java
  64. 35
      core/src/main/java/com/databasir/core/domain/project/converter/ProjectResponseConverter.java
  65. 16
      core/src/main/java/com/databasir/core/domain/project/data/DataSourcePropertyValue.java
  66. 59
      core/src/main/java/com/databasir/core/domain/project/data/ProjectCreateRequest.java
  67. 56
      core/src/main/java/com/databasir/core/domain/project/data/ProjectDetailResponse.java
  68. 43
      core/src/main/java/com/databasir/core/domain/project/data/ProjectListCondition.java
  69. 22
      core/src/main/java/com/databasir/core/domain/project/data/ProjectSimpleResponse.java
  70. 56
      core/src/main/java/com/databasir/core/domain/project/data/ProjectUpdateRequest.java
  71. 141
      core/src/main/java/com/databasir/core/domain/project/service/ProjectService.java
  72. 17
      core/src/main/java/com/databasir/core/domain/system/data/SystemEmailResponse.java
  73. 24
      core/src/main/java/com/databasir/core/domain/system/data/SystemEmailUpdateRequest.java
  74. 106
      core/src/main/java/com/databasir/core/domain/system/service/SystemService.java
  75. 13
      core/src/main/java/com/databasir/core/domain/user/converter/UserPojoConverter.java
  76. 32
      core/src/main/java/com/databasir/core/domain/user/converter/UserResponseConverter.java
  77. 28
      core/src/main/java/com/databasir/core/domain/user/data/UserCreateRequest.java
  78. 39
      core/src/main/java/com/databasir/core/domain/user/data/UserDetailResponse.java
  79. 16
      core/src/main/java/com/databasir/core/domain/user/data/UserLoginRequest.java
  80. 32
      core/src/main/java/com/databasir/core/domain/user/data/UserLoginResponse.java
  81. 15
      core/src/main/java/com/databasir/core/domain/user/data/UserNicknameUpdateRequest.java
  82. 51
      core/src/main/java/com/databasir/core/domain/user/data/UserPageCondition.java
  83. 27
      core/src/main/java/com/databasir/core/domain/user/data/UserPageResponse.java
  84. 22
      core/src/main/java/com/databasir/core/domain/user/data/UserPasswordUpdateRequest.java
  85. 13
      core/src/main/java/com/databasir/core/domain/user/data/UserRoleAssignRequest.java
  86. 146
      core/src/main/java/com/databasir/core/domain/user/service/UserService.java
  87. 15
      core/src/main/java/com/databasir/core/infrastructure/connection/DatabaseConnectionFactory.java
  88. 39
      core/src/main/java/com/databasir/core/infrastructure/connection/DatabaseConnectionService.java
  89. 10
      core/src/main/java/com/databasir/core/infrastructure/connection/DatabaseTypes.java
  90. 39
      core/src/main/java/com/databasir/core/infrastructure/connection/MysqlDatabaseConnectionFactory.java
  91. 39
      core/src/main/java/com/databasir/core/infrastructure/connection/PostgresqlDatabaseConnectionFactory.java
  92. 65
      core/src/main/java/com/databasir/core/infrastructure/converter/JsonConverter.java
  93. 61
      core/src/main/java/com/databasir/core/infrastructure/jwt/JwtTokens.java
  94. 36
      core/src/main/java/com/databasir/core/infrastructure/mail/MailSender.java
  95. 5
      core/src/main/java/com/databasir/core/infrastructure/meta/DatabaseMetaResolver.java
  96. 77
      dao/build.gradle
  97. 159
      dao/generated-src/jooq/main/java/com/databasir/dao/Databasir.java
  98. 45
      dao/generated-src/jooq/main/java/com/databasir/dao/DefaultCatalog.java
  99. 39
      dao/generated-src/jooq/main/java/com/databasir/dao/Indexes.java
  100. 83
      dao/generated-src/jooq/main/java/com/databasir/dao/Keys.java

5
api.gradle

@ -0,0 +1,5 @@
rootProject.name = 'api'
include 'plugin'
include 'common'
include 'dao'
include 'core'

10
api/Dockerfile

@ -0,0 +1,10 @@
FROM openjdk:11.0.13-jre
ARG service_name_folder
WORKDIR /app
ADD databasir.jar /app
EXPOSE 8080
#-Ddatabasir.datasource.username=${databasir.datasource.username}
#-Ddatabasir.datasource.password=${databasir.datasource.password}
#-Ddatabasir.datasource.url=${databasir.datasource.url}
ENTRYPOINT ["sh", "-c","java ${JAVA_OPTS} -jar /app/databasir.jar"]

41
api/build.gradle

@ -0,0 +1,41 @@
plugins {
id 'io.spring.dependency-management'
id 'org.springframework.boot' apply false
}
bootJar {
archiveBaseName = 'databasir'
archiveVersion = ''
enabled = true
}
bootBuildImage {
imageName = "${project.group}/databasir:${project.version}"
publish = false
}
dependencies {
implementation project(":common")
implementation project(":plugin")
implementation project(":core")
implementation project(":dao")
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation 'org.springframework.boot:spring-boot-starter-aop'
implementation 'org.springframework.boot:spring-boot-starter-data-jdbc'
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.flywaydb:flyway-core'
}
/**
* Docker
*/
task copyDockerfile(type: Copy) {
from("Dockerfile")
into("build/libs")
}
bootJar.finalizedBy copyDockerfile
assemble.finalizedBy copyDockerfile

13
api/src/main/java/com/databasir/DatabasirApplication.java

@ -0,0 +1,13 @@
package com.databasir;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.r2dbc.R2dbcAutoConfiguration;
@SpringBootApplication(exclude = {R2dbcAutoConfiguration.class})
public class DatabasirApplication {
public static void main(String[] args) {
SpringApplication.run(DatabasirApplication.class, args);
}
}

44
api/src/main/java/com/databasir/api/DocumentController.java

@ -0,0 +1,44 @@
package com.databasir.api;
import com.databasir.common.JsonData;
import com.databasir.core.domain.document.data.DatabaseDocumentResponse;
import com.databasir.core.domain.document.data.DatabaseDocumentVersionResponse;
import com.databasir.core.domain.document.service.DocumentService;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.web.PageableDefault;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
@RestController
@RequiredArgsConstructor
@Validated
public class DocumentController {
private final DocumentService documentService;
@PostMapping(Routes.Document.SYNC_ONE)
public JsonData<Void> sync(@PathVariable Integer projectId) {
documentService.syncByProjectId(projectId);
return JsonData.ok();
}
@GetMapping(Routes.Document.GET_ONE)
public JsonData<DatabaseDocumentResponse> getByProjectId(@PathVariable Integer projectId,
@RequestParam(required = false) Long version) {
return documentService.getOneByProjectId(projectId, version)
.map(JsonData::ok)
.orElseGet(JsonData::ok);
}
@GetMapping(Routes.Document.LIST_VERSIONS)
public JsonData<Page<DatabaseDocumentVersionResponse>> getVersionsByProjectId(@PathVariable Integer projectId,
@PageableDefault(sort = "id", direction = Sort.Direction.DESC)
Pageable page) {
return JsonData.ok(documentService.getVersionsBySchemaSourceId(projectId, page));
}
}

106
api/src/main/java/com/databasir/api/GroupController.java

@ -0,0 +1,106 @@
package com.databasir.api;
import com.databasir.api.validator.UserOperationValidator;
import com.databasir.common.JsonData;
import com.databasir.core.domain.group.data.*;
import com.databasir.core.domain.group.service.GroupService;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.web.PageableDefault;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import java.util.Arrays;
import java.util.List;
@RestController
@RequiredArgsConstructor
@Validated
public class GroupController {
private final GroupService groupService;
private final UserOperationValidator userOperationValidator;
@PostMapping(Routes.Group.CREATE)
@PreAuthorize("hasAnyAuthority('SYS_OWNER')")
public JsonData<Void> create(@RequestBody @Valid GroupCreateRequest request) {
groupService.create(request);
return JsonData.ok();
}
@PatchMapping(Routes.Group.UPDATE)
@PreAuthorize("hasAnyAuthority('SYS_OWNER', 'GROUP_OWNER'.concat('?groupId='.concat(#request.id)))")
public JsonData<Void> update(@RequestBody @Valid GroupUpdateRequest request) {
groupService.update(request);
return JsonData.ok();
}
@GetMapping(Routes.Group.LIST)
public JsonData<Page<GroupPageResponse>> list(@PageableDefault(sort = "id", direction = Sort.Direction.DESC)
Pageable page,
GroupPageCondition condition) {
return JsonData.ok(groupService.list(page, condition));
}
@DeleteMapping(Routes.Group.DELETE)
@PreAuthorize("hasAnyAuthority('SYS_OWNER', 'GROUP_OWNER'.concat('?groupId='.concat(#groupId)))")
public JsonData<Void> deleteById(@PathVariable Integer groupId) {
groupService.delete(groupId);
return JsonData.ok();
}
@GetMapping(Routes.Group.GET_ONE)
public JsonData<GroupResponse> getOne(@PathVariable Integer groupId) {
return JsonData.ok(groupService.get(groupId));
}
@GetMapping(Routes.Group.MEMBERS)
public JsonData<Page<GroupMemberPageResponse>> listGroupMembers(@PathVariable Integer groupId,
@PageableDefault(sort = "user_role.create_at", direction = Sort.Direction.DESC)
Pageable pageable,
GroupMemberPageCondition condition) {
return JsonData.ok(groupService.listGroupMembers(groupId, pageable, condition));
}
@PostMapping(Routes.Group.ADD_MEMBER)
@PreAuthorize("hasAnyAuthority('SYS_OWNER', 'GROUP_OWNER'.concat('?groupId='.concat(#groupId)))")
public JsonData<Void> addGroupMember(@PathVariable Integer groupId,
@RequestBody @Valid GroupMemberCreateRequest request) {
userOperationValidator.forbiddenIfUpdateSelfRole(request.getUserId());
List<String> groupRoles = Arrays.asList("GROUP_OWNER", "GROUP_MEMBER");
if (!groupRoles.contains(request.getRole())) {
throw new IllegalArgumentException("role should be GROUP_OWNER or GROUP_MEMBER");
}
groupService.addMember(groupId, request);
return JsonData.ok();
}
@DeleteMapping(Routes.Group.DELETE_MEMBER)
@PreAuthorize("hasAnyAuthority('SYS_OWNER', 'GROUP_OWNER'.concat('?groupId='.concat(#groupId)))")
public JsonData<Void> removeGroupMember(@PathVariable Integer groupId,
@PathVariable Integer userId) {
userOperationValidator.forbiddenIfUpdateSelfRole(userId);
groupService.removeMember(groupId, userId);
return JsonData.ok();
}
@PatchMapping(Routes.Group.UPDATE_MEMBER)
@PreAuthorize("hasAnyAuthority('SYS_OWNER', 'GROUP_OWNER'.concat('?groupId='.concat(#groupId)))")
public JsonData<Void> updateGroupMemberRole(@PathVariable Integer groupId,
@PathVariable Integer userId,
@RequestBody GroupMemberRoleUpdateRequest request) {
userOperationValidator.forbiddenIfUpdateSelfRole(userId);
List<String> groupRoles = Arrays.asList("GROUP_OWNER", "GROUP_MEMBER");
if (!groupRoles.contains(request.getRole())) {
throw new IllegalArgumentException("role should be GROUP_OWNER or GROUP_MEMBER");
}
groupService.changeMemberRole(groupId, userId, request.getRole());
return JsonData.ok();
}
}

55
api/src/main/java/com/databasir/api/LoginController.java

@ -0,0 +1,55 @@
package com.databasir.api;
import com.databasir.common.DatabasirException;
import com.databasir.common.JsonData;
import com.databasir.common.exception.InvalidTokenException;
import com.databasir.core.domain.DomainErrors;
import com.databasir.core.domain.login.data.AccessTokenRefreshRequest;
import com.databasir.core.domain.login.data.AccessTokenRefreshResponse;
import com.databasir.core.domain.login.service.LoginService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
import java.util.Objects;
@RestController
@RequiredArgsConstructor
@Validated
@Slf4j
public class LoginController {
private final AuthenticationManager authenticationManager;
private final LoginService loginService;
@GetMapping(Routes.Login.LOGOUT)
public JsonData<Void> logout() {
SecurityContextHolder.clearContext();
return JsonData.ok();
}
@PostMapping(Routes.Login.REFRESH_ACCESS_TOKEN)
public JsonData<AccessTokenRefreshResponse> refreshAccessTokens(@RequestBody @Valid AccessTokenRefreshRequest request,
HttpServletResponse response) {
try {
return JsonData.ok(loginService.refreshAccessTokens(request));
} catch (DatabasirException e) {
if (Objects.equals(e.getErrCode(), DomainErrors.ACCESS_TOKEN_REFRESH_INVALID.getErrCode())) {
throw new InvalidTokenException(DomainErrors.ACCESS_TOKEN_REFRESH_INVALID);
}
if (Objects.equals(e.getErrCode(), DomainErrors.REFRESH_TOKEN_EXPIRED.getErrCode())) {
throw new InvalidTokenException(DomainErrors.REFRESH_TOKEN_EXPIRED);
}
throw e;
}
}
}

58
api/src/main/java/com/databasir/api/ProjectController.java

@ -0,0 +1,58 @@
package com.databasir.api;
import com.databasir.common.JsonData;
import com.databasir.core.domain.project.data.*;
import com.databasir.core.domain.project.service.ProjectService;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.web.PageableDefault;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
@RestController
@RequiredArgsConstructor
@Validated
public class ProjectController {
private final ProjectService projectService;
@PostMapping(Routes.GroupProject.CREATE)
@PreAuthorize("hasAnyAuthority('SYS_OWNER', 'GROUP_OWNER?groupId='+#request.groupId, 'GROUP_MEMBER?groupId='+#request.groupId)")
public JsonData<Void> create(@RequestBody @Valid ProjectCreateRequest request) {
projectService.create(request);
return JsonData.ok();
}
@PatchMapping(Routes.GroupProject.UPDATE)
@PreAuthorize("hasAnyAuthority('SYS_OWNER', 'GROUP_OWNER?groupId='+#groupId, 'GROUP_MEMBER?groupId='+#groupId)")
public JsonData<Void> update(@RequestBody @Valid ProjectUpdateRequest request,
@PathVariable Integer groupId) {
projectService.update(groupId, request);
return JsonData.ok();
}
@DeleteMapping(Routes.GroupProject.DELETE)
@PreAuthorize("hasAnyAuthority('SYS_OWNER', 'GROUP_OWNER?groupId='+#groupId, 'GROUP_MEMBER?groupId='+#groupId)")
public JsonData<Void> delete(@PathVariable Integer groupId,
@PathVariable Integer projectId) {
projectService.delete(projectId);
return JsonData.ok();
}
@GetMapping(Routes.GroupProject.GET_ONE)
public JsonData<ProjectDetailResponse> getOne(@PathVariable Integer projectId) {
return JsonData.ok(projectService.getOne(projectId));
}
@GetMapping(Routes.GroupProject.LIST)
public JsonData<Page<ProjectSimpleResponse>> list(@PageableDefault(sort = "id", direction = Sort.Direction.DESC)
Pageable page,
ProjectListCondition condition) {
return JsonData.ok(projectService.list(page, condition));
}
}

84
api/src/main/java/com/databasir/api/Routes.java

@ -0,0 +1,84 @@
package com.databasir.api;
public interface Routes {
String BASE = "/api/v1.0";
interface User {
String LIST = BASE + "/users";
String GET_ONE = BASE + "/users/{userId}";
String ENABLE = BASE + "/users/{userId}/enable";
String DISABLE = BASE + "/users/{userId}/disable";
String CREATE = BASE + "/users";
String UPDATE_PASSWORD = BASE + "/users/{userId}/password";
String UPDATE_NICKNAME = BASE + "/users/{userId}/nickname";
String RENEW_PASSWORD = BASE + "/users/{userId}/renew_password";
String ADD_OR_REMOVE_SYS_OWNER = BASE + "/users/{userId}/sys_owners";
}
interface Group {
String LIST = BASE + "/groups";
String GET_ONE = BASE + "/groups/{groupId}";
String CREATE = BASE + "/groups";
String UPDATE = BASE + "/groups";
String DELETE = BASE + "/groups/{groupId}";
String MEMBERS = GET_ONE + "/members";
String DELETE_MEMBER = GET_ONE + "/members/{userId}";
String ADD_MEMBER = GET_ONE + "/members";
String UPDATE_MEMBER = GET_ONE + "/members/{userId}";
}
interface GroupProject {
String LIST = BASE + "/projects";
String GET_ONE = BASE + "/projects/{projectId}";
String CREATE = BASE + "/projects";
String UPDATE = BASE + "/groups/{groupId}/projects";
String DELETE = BASE + "/groups/{groupId}/projects/{projectId}";
}
interface Document {
String GET_ONE = BASE + "/projects/{projectId}/documents";
String SYNC_ONE = BASE + "/projects/{projectId}/documents";
String LIST_VERSIONS = BASE + "/projects/{projectId}/document_versions";
}
interface Setting {
String GET_SYS_EMAIL = BASE + "/settings/sys_email";
String UPDATE_SYS_EMAIL = BASE + "/settings/sys_email";
}
interface Login {
String LOGOUT = "/logout";
String REFRESH_ACCESS_TOKEN = "/access_tokens";
}
}

35
api/src/main/java/com/databasir/api/SettingController.java

@ -0,0 +1,35 @@
package com.databasir.api;
import com.databasir.common.JsonData;
import com.databasir.core.domain.system.data.SystemEmailResponse;
import com.databasir.core.domain.system.data.SystemEmailUpdateRequest;
import com.databasir.core.domain.system.service.SystemService;
import lombok.RequiredArgsConstructor;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
@RestController
@RequiredArgsConstructor
@Validated
@PreAuthorize("hasAnyAuthority('SYS_OWNER')")
public class SettingController {
private final SystemService systemService;
@GetMapping(Routes.Setting.GET_SYS_EMAIL)
public JsonData<SystemEmailResponse> getSystemEmailSetting() {
return systemService.getEmailSetting()
.map(JsonData::ok)
.orElseGet(JsonData::ok);
}
@PostMapping(Routes.Setting.UPDATE_SYS_EMAIL)
public JsonData<Void> updateSystemEmailSetting(@RequestBody @Valid SystemEmailUpdateRequest request) {
systemService.updateEmailSetting(request);
return JsonData.ok();
}
}

105
api/src/main/java/com/databasir/api/UserController.java

@ -0,0 +1,105 @@
package com.databasir.api;
import com.databasir.api.validator.UserOperationValidator;
import com.databasir.common.JsonData;
import com.databasir.common.exception.Forbidden;
import com.databasir.core.domain.user.data.*;
import com.databasir.core.domain.user.service.UserService;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.web.PageableDefault;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
@RestController
@RequiredArgsConstructor
@Validated
public class UserController {
private final UserService userService;
private final UserOperationValidator userOperationValidator;
@GetMapping(Routes.User.LIST)
public JsonData<Page<UserPageResponse>> list(@PageableDefault(sort = "id", direction = Sort.Direction.DESC)
Pageable pageable,
UserPageCondition condition) {
return JsonData.ok(userService.list(pageable, condition));
}
@PostMapping(Routes.User.DISABLE)
@PreAuthorize("hasAnyAuthority('SYS_OWNER')")
public JsonData<Void> disableUser(@PathVariable Integer userId) {
userService.switchEnableStatus(userId, false);
return JsonData.ok();
}
@PostMapping(Routes.User.ENABLE)
@PreAuthorize("hasAnyAuthority('SYS_OWNER')")
public JsonData<Void> enableUser(@PathVariable Integer userId) {
userService.switchEnableStatus(userId, true);
return JsonData.ok();
}
@PostMapping(Routes.User.CREATE)
@PreAuthorize("hasAnyAuthority('SYS_OWNER')")
public JsonData<Void> create(@RequestBody @Valid UserCreateRequest request) {
userService.create(request);
return JsonData.ok();
}
@GetMapping(Routes.User.GET_ONE)
public JsonData<UserDetailResponse> getOne(@PathVariable Integer userId) {
return JsonData.ok(userService.get(userId));
}
@PostMapping(Routes.User.RENEW_PASSWORD)
@PreAuthorize("hasAnyAuthority('SYS_OWNER')")
public JsonData<Void> renewPassword(@PathVariable Integer userId) {
userService.renewPassword(userId);
return JsonData.ok();
}
@PostMapping(Routes.User.ADD_OR_REMOVE_SYS_OWNER)
@PreAuthorize("hasAnyAuthority('SYS_OWNER')")
public JsonData<Void> addSysOwner(@PathVariable Integer userId) {
userOperationValidator.forbiddenIfUpdateSelfRole(userId);
userService.addSysOwnerTo(userId);
return JsonData.ok();
}
@DeleteMapping(Routes.User.ADD_OR_REMOVE_SYS_OWNER)
@PreAuthorize("hasAnyAuthority('SYS_OWNER')")
public JsonData<Void> removeSysOwner(@PathVariable Integer userId) {
userOperationValidator.forbiddenIfUpdateSelfRole(userId);
userService.removeSysOwnerFrom(userId);
return JsonData.ok();
}
@PostMapping(Routes.User.UPDATE_PASSWORD)
public JsonData<Void> updatePassword(@PathVariable Integer userId,
@RequestBody @Valid UserPasswordUpdateRequest request) {
if (userOperationValidator.isMyself(userId)) {
userService.updatePassword(userId, request);
return JsonData.ok();
} else {
throw new Forbidden();
}
}
@PostMapping(Routes.User.UPDATE_NICKNAME)
public JsonData<Void> updateNickname(@PathVariable Integer userId,
@RequestBody @Valid UserNicknameUpdateRequest request) {
if (userOperationValidator.isMyself(userId)) {
userService.updateNickname(userId, request);
return JsonData.ok();
} else {
throw new Forbidden();
}
}
}

234
api/src/main/java/com/databasir/api/advice/DatabasirExceptionAdvice.java

@ -0,0 +1,234 @@
package com.databasir.api.advice;
import com.databasir.common.DatabasirException;
import com.databasir.common.JsonData;
import com.databasir.common.SystemException;
import com.databasir.common.exception.Forbidden;
import com.databasir.common.exception.InvalidTokenException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.TypeMismatchException;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.validation.ObjectError;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.ServletRequestBindingException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.multipart.support.MissingServletRequestPartException;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.util.List;
import java.util.Set;
@RestControllerAdvice
@Slf4j
public class DatabasirExceptionAdvice extends ResponseEntityExceptionHandler {
@ExceptionHandler({ConstraintViolationException.class})
public ResponseEntity<Object> handleConstraintViolationException(
ConstraintViolationException constraintViolationException, WebRequest request) {
String errorMsg = "";
String path = getPath(request);
Set<ConstraintViolation<?>> violations = constraintViolationException.getConstraintViolations();
for (ConstraintViolation<?> item : violations) {
errorMsg = item.getMessage();
log.warn("ConstraintViolationException, request: {}, exception: {}, invalid value: {}",
path, errorMsg, item.getInvalidValue());
break;
}
return handleNon200Response(errorMsg, HttpStatus.BAD_REQUEST, path);
}
@ExceptionHandler({InvalidTokenException.class})
protected ResponseEntity<Object> handleInvalidTokenException(InvalidTokenException ex, WebRequest request) {
String path = getPath(request);
log.warn("handle InvalidTokenException " + path + ", " + ex);
JsonData<Object> data = JsonData.error(ex.getErrCode(), ex.getErrMessage());
return handleNon200Response(ex.getMessage(), HttpStatus.UNAUTHORIZED, path, data);
}
@ExceptionHandler({IllegalArgumentException.class})
protected ResponseEntity<Object> handleIllegalArgument(IllegalArgumentException ex, WebRequest request) {
String path = getPath(request);
log.warn("handle illegalArgument " + path, ex);
return handleNon200Response(ex.getMessage(), HttpStatus.BAD_REQUEST, path);
}
@ExceptionHandler({AccessDeniedException.class})
protected ResponseEntity<Object> handleAccessDeniedException(AccessDeniedException ex, WebRequest request) {
String path = getPath(request);
log.warn("AccessDeniedException, request: {}, exception: {}", path, ex.getMessage());
return handleNon200Response(ex.getMessage(), HttpStatus.FORBIDDEN, path);
}
@ExceptionHandler({Forbidden.class})
protected ResponseEntity<Object> handleForbiddenException(Forbidden ex, WebRequest request) {
String path = getPath(request);
log.warn("Forbidden, request: {}, exception: {}", path, ex.getMessage());
return handleNon200Response(ex.getMessage(), HttpStatus.FORBIDDEN, path);
}
@ExceptionHandler(value = DatabasirException.class)
public ResponseEntity<Object> handleBusinessException(
DatabasirException databasirException, WebRequest request) {
String path = getPath(request);
JsonData<Void> body = JsonData.error(databasirException.getErrCode(), databasirException.getErrMessage());
if (databasirException.getCause() == null) {
log.warn("BusinessException, request: {}, exception: {}", path, databasirException.getErrMessage());
} else {
log.warn("BusinessException, request: " + path, databasirException);
}
return ResponseEntity.ok().body(body);
}
@ExceptionHandler({SystemException.class})
public ResponseEntity<Object> handleSystemException(SystemException systemException, WebRequest request) {
String path = getPath(request);
if (systemException.getCause() != null) {
log.error("SystemException, request: " + path
+ ", exception: " + systemException.getMessage() + ", caused by:", systemException.getCause());
} else {
log.error("SystemException, request: " + path + ", exception: " + systemException.getMessage());
}
return handleNon200Response(systemException.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR, path);
}
@ExceptionHandler({Exception.class})
public ResponseEntity<Object> handleUnspecificException(Exception ex, WebRequest request) {
String path = getPath(request);
String errorMsg = ex.getMessage();
log.error("Unspecific exception, request: " + path + ", exception: " + errorMsg + ":", ex);
return handleNon200Response(errorMsg, HttpStatus.INTERNAL_SERVER_ERROR, path);
}
@Override
public ResponseEntity<Object> handleBindException(
BindException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
String errorMsg = buildMessages(ex.getBindingResult());
log.warn("BindException, request: {}, exception: {}", getPath(request), errorMsg);
return handleOverriddenException(ex, headers, status, request, errorMsg);
}
@Override
public ResponseEntity<Object> handleMethodArgumentNotValid(
MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
String errorMsg = buildMessages(ex.getBindingResult());
log.warn("MethodArgumentNotValidException, request: {}, exception: {}", getPath(request), errorMsg);
return handleOverriddenException(ex, headers, status, request, errorMsg);
}
@Override
public ResponseEntity<Object> handleTypeMismatch(
TypeMismatchException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
log.warn("TypeMismatchException, request: {}, exception: {}", getPath(request), ex.getMessage());
return handleOverriddenException(ex, headers, status, request, ex.getMessage());
}
@Override
public ResponseEntity<Object> handleMissingServletRequestParameter(
MissingServletRequestParameterException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
log.warn("MissingServletRequestParameterException, request: {}, exception: {}",
getPath(request), ex.getMessage());
return handleOverriddenException(ex, headers, status, request, ex.getMessage());
}
@Override
public ResponseEntity<Object> handleMissingServletRequestPart(
MissingServletRequestPartException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
log.warn("MissingServletRequestPartException, request: {}, exception: {}", getPath(request), ex.getMessage());
return handleOverriddenException(ex, headers, status, request, ex.getMessage());
}
@Override
public ResponseEntity<Object> handleHttpMessageNotReadable(
HttpMessageNotReadableException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
String errorMsg = ex.getMostSpecificCause().getMessage();
log.warn("HttpMessageNotReadableException, request: {}, exception: {}", getPath(request), errorMsg);
return handleOverriddenException(ex, headers, status, request, errorMsg);
}
@Override
public ResponseEntity<Object> handleServletRequestBindingException(
ServletRequestBindingException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
log.warn("ServletRequestBindingException, request: {}, exception: {}", getPath(request), ex.getMessage());
return handleOverriddenException(ex, headers, status, request, ex.getMessage());
}
@Override
public ResponseEntity<Object> handleHttpRequestMethodNotSupported(
HttpRequestMethodNotSupportedException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
String errorMsg = ex.getMessage();
log.warn("HttpRequestMethodNotSupportedException, request: {}, exception: {}", getPath(request), errorMsg);
return handleOverriddenException(ex, headers, status, request, ex.getMessage());
}
@ExceptionHandler({BadCredentialsException.class})
protected ResponseEntity<Object> handleBadCredentialsException(BadCredentialsException ex, WebRequest request) {
String path = getPath(request);
JsonData<Void> body = JsonData.error("-1", "用户名或密码错误");
log.warn("BadCredentialsException, request: {}, exception: {}", path, ex.getMessage());
return ResponseEntity.ok().body(body);
}
private String buildMessages(BindingResult result) {
StringBuilder resultBuilder = new StringBuilder();
List<ObjectError> errors = result.getAllErrors();
for (ObjectError error : errors) {
if (error instanceof FieldError) {
FieldError fieldError = (FieldError) error;
String fieldName = fieldError.getField();
String fieldErrMsg = fieldError.getDefaultMessage();
resultBuilder.append(fieldName).append(" ").append(fieldErrMsg);
}
}
return resultBuilder.toString();
}
private ResponseEntity<Object> handleNon200Response(String errorMsg, HttpStatus httpStatus, String path) {
return ResponseEntity.status(httpStatus).body(null);
}
private ResponseEntity<Object> handleNon200Response(String errorMsg,
HttpStatus httpStatus,
String path,
Object body) {
return ResponseEntity.status(httpStatus).body(body);
}
private ResponseEntity<Object> handleOverriddenException(
Exception ex, HttpHeaders headers, HttpStatus status, WebRequest request, String errorMsg) {
return handleExceptionInternal(ex, null, headers, status, request);
}
private String getPath(WebRequest request) {
String description = request.getDescription(false);
return description.startsWith("uri=") ? description.substring(4) : description;
}
}

75
api/src/main/java/com/databasir/api/config/SecurityConfig.java

@ -0,0 +1,75 @@
package com.databasir.api.config;
import com.databasir.api.Routes;
import com.databasir.api.config.security.*;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@Configuration
@RequiredArgsConstructor
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private final DatabasirUserDetailService databasirUserDetailService;
private final DatabasirAuthenticationEntryPoint databasirAuthenticationEntryPoint;
private final DatabasirJwtTokenFilter databasirJwtTokenFilter;
private final DatabasirAuthenticationFailureHandler databasirAuthenticationFailureHandler;
private final DatabasirAuthenticationSuccessHandler databasirAuthenticationSuccessHandler;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.headers().frameOptions().disable();
http.csrf().disable();
http.cors();
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.formLogin()
.loginProcessingUrl("/login")
.failureHandler(databasirAuthenticationFailureHandler)
.successHandler(databasirAuthenticationSuccessHandler)
.and()
.authorizeRequests()
.antMatchers("/login", Routes.Login.REFRESH_ACCESS_TOKEN).permitAll()
.anyRequest().authenticated()
.and()
.exceptionHandling().authenticationEntryPoint(databasirAuthenticationEntryPoint);
http.addFilterBefore(
databasirJwtTokenFilter,
UsernamePasswordAuthenticationFilter.class
);
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(databasirUserDetailService)
.passwordEncoder(bCryptPasswordEncoder());
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Bean
public PasswordEncoder bCryptPasswordEncoder() {
return new BCryptPasswordEncoder();
}
}

18
api/src/main/java/com/databasir/api/config/WebConfig.java

@ -0,0 +1,18 @@
package com.databasir.api.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.web.config.EnableSpringDataWebSupport;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
@Configuration
@EnableSpringDataWebSupport
public class WebConfig extends WebMvcConfigurerAdapter {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("*")
.allowedMethods("GET", "POST", "DELETE", "PATCH", "PUT");
}
}

26
api/src/main/java/com/databasir/api/config/security/DatabasirAuthenticationEntryPoint.java

@ -0,0 +1,26 @@
package com.databasir.api.config.security;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Service;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Service
@RequiredArgsConstructor
@Slf4j
public class DatabasirAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(javax.servlet.http.HttpServletRequest request,
javax.servlet.http.HttpServletResponse response,
AuthenticationException authException) throws IOException, ServletException {
log.warn("验证未通过. 提示信息 - {}", authException.getMessage());
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, authException.getMessage());
}
}

49
api/src/main/java/com/databasir/api/config/security/DatabasirAuthenticationFailureHandler.java

@ -0,0 +1,49 @@
package com.databasir.api.config.security;
import com.databasir.common.JsonData;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.DisabledException;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
@Component
@RequiredArgsConstructor
public class DatabasirAuthenticationFailureHandler implements AuthenticationFailureHandler {
private final ObjectMapper objectMapper;
@Override
public void onAuthenticationFailure(HttpServletRequest request,
HttpServletResponse response,
AuthenticationException exception) throws IOException, ServletException {
response.setCharacterEncoding(StandardCharsets.UTF_8.name());
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
if (exception instanceof BadCredentialsException) {
JsonData<Void> data = JsonData.error("-1", "用户名或密码错误");
String jsonString = objectMapper.writeValueAsString(data);
response.setStatus(HttpStatus.OK.value());
response.getOutputStream().write(jsonString.getBytes(StandardCharsets.UTF_8));
} else if (exception instanceof DisabledException) {
JsonData<Void> data = JsonData.error("-1", "用户已禁用");
String jsonString = objectMapper.writeValueAsString(data);
response.setStatus(HttpStatus.OK.value());
response.getOutputStream().write(jsonString.getBytes(StandardCharsets.UTF_8));
} else {
JsonData<Void> data = JsonData.error("-1", "未登录或未授权用户");
String jsonString = objectMapper.writeValueAsString(data);
response.setStatus(HttpStatus.UNAUTHORIZED.value());
response.getOutputStream().write(jsonString.getBytes(StandardCharsets.UTF_8));
}
}
}

61
api/src/main/java/com/databasir/api/config/security/DatabasirAuthenticationSuccessHandler.java

@ -0,0 +1,61 @@
package com.databasir.api.config.security;
import com.databasir.common.JsonData;
import com.databasir.core.domain.login.data.LoginKeyResponse;
import com.databasir.core.domain.login.service.LoginService;
import com.databasir.core.domain.user.data.UserLoginResponse;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import org.springframework.http.MediaType;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.time.ZoneId;
import java.util.List;
import java.util.stream.Collectors;
@Component
@RequiredArgsConstructor
public class DatabasirAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
private final ObjectMapper objectMapper;
private final LoginService loginService;
@Override
public void onAuthenticationSuccess(HttpServletRequest request,
HttpServletResponse response,
Authentication authentication) throws IOException, ServletException {
DatabasirUserDetails user = (DatabasirUserDetails) authentication.getPrincipal();
List<UserLoginResponse.RoleResponse> roles = user.getRoles()
.stream()
.map(ur -> {
UserLoginResponse.RoleResponse data = new UserLoginResponse.RoleResponse();
data.setRole(ur.getRole());
data.setGroupId(ur.getGroupId());
return data;
})
.collect(Collectors.toList());
response.setCharacterEncoding(StandardCharsets.UTF_8.name());
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
UserLoginResponse data = new UserLoginResponse();
data.setId(user.getUserPojo().getId());
data.setNickname(user.getUserPojo().getNickname());
data.setEmail(user.getUserPojo().getEmail());
data.setUsername(user.getUsername());
LoginKeyResponse loginKey = loginService.generate(user.getUserPojo().getId());
data.setAccessToken(loginKey.getAccessToken());
long expireAt = loginKey.getAccessTokenExpireAt().atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
data.setAccessTokenExpireAt(expireAt);
data.setRefreshToken(loginKey.getRefreshToken());
data.setRoles(roles);
objectMapper.writeValue(response.getWriter(), JsonData.ok(data));
}
}

56
api/src/main/java/com/databasir/api/config/security/DatabasirJwtTokenFilter.java

@ -0,0 +1,56 @@
package com.databasir.api.config.security;
import com.databasir.core.infrastructure.jwt.JwtTokens;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpHeaders;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Component
@RequiredArgsConstructor
public class DatabasirJwtTokenFilter extends OncePerRequestFilter {
private static final String SPACE = " ";
private final JwtTokens jwtTokens;
private final UserDetailsService userDetailsService;
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
final String header = request.getHeader(HttpHeaders.AUTHORIZATION);
if (!StringUtils.hasText(header) || !header.startsWith(JwtTokens.TOKEN_PREFIX)) {
filterChain.doFilter(request, response);
return;
}
final String token = header.split(SPACE)[1].trim();
if (!jwtTokens.verify(token)) {
filterChain.doFilter(request, response);
return;
}
String username = jwtTokens.getUsername(token);
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authentication);
filterChain.doFilter(request, response);
}
}

34
api/src/main/java/com/databasir/api/config/security/DatabasirUserDetailService.java

@ -0,0 +1,34 @@
package com.databasir.api.config.security;
import com.databasir.dao.impl.LoginDao;
import com.databasir.dao.impl.UserDao;
import com.databasir.dao.impl.UserRoleDao;
import com.databasir.dao.tables.pojos.UserPojo;
import com.databasir.dao.tables.pojos.UserRolePojo;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import java.util.Collections;
import java.util.List;
@Service
@RequiredArgsConstructor
public class DatabasirUserDetailService implements UserDetailsService {
private final UserDao userDao;
private final UserRoleDao userRoleDao;
private final LoginDao loginDao;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
UserPojo user = userDao.selectByEmailOrUsername(username)
.orElseThrow(() -> new UsernameNotFoundException("用户名或密码错误"));
List<UserRolePojo> roles = userRoleDao.selectByUserIds(Collections.singletonList(user.getId()));
return new DatabasirUserDetails(user, roles);
}
}

67
api/src/main/java/com/databasir/api/config/security/DatabasirUserDetails.java

@ -0,0 +1,67 @@
package com.databasir.api.config.security;
import com.databasir.dao.tables.pojos.UserPojo;
import com.databasir.dao.tables.pojos.UserRolePojo;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;
@RequiredArgsConstructor
public class DatabasirUserDetails implements UserDetails {
@Getter
private final UserPojo userPojo;
@Getter
private final List<UserRolePojo> roles;
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return roles.stream()
.map(role -> {
String expression = role.getRole();
if (role.getGroupId() != null) {
expression += "?groupId=" + role.getGroupId();
}
return new SimpleGrantedAuthority(expression);
})
.collect(Collectors.toList());
}
@Override
public String getPassword() {
return userPojo.getPassword();
}
@Override
public String getUsername() {
return userPojo.getEmail();
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return userPojo.getEnabled();
}
}

27
api/src/main/java/com/databasir/api/validator/UserOperationValidator.java

@ -0,0 +1,27 @@
package com.databasir.api.validator;
import com.databasir.api.config.security.DatabasirUserDetails;
import com.databasir.core.domain.DomainErrors;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
@Component
public class UserOperationValidator {
public void forbiddenIfUpdateSelfRole(Integer userId) {
DatabasirUserDetails principal = (DatabasirUserDetails) SecurityContextHolder.getContext()
.getAuthentication()
.getPrincipal();
if (principal.getUserPojo().getId().equals(userId)) {
throw DomainErrors.CANNOT_UPDATE_SELF_ROLE.exception();
}
}
public boolean isMyself(Integer userId) {
DatabasirUserDetails principal = (DatabasirUserDetails) SecurityContextHolder.getContext()
.getAuthentication()
.getPrincipal();
return principal.getUserPojo().getId().equals(userId);
}
}

11
api/src/main/resources/application-local.properties

@ -0,0 +1,11 @@
server.port=8080
logging.level.org.jooq=DEBUG
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.url=jdbc:mysql://localhost:3306/databasir
spring.jooq.sql-dialect=mysql
spring.flyway.enabled=true
spring.flyway.baseline-on-migrate=true
spring.flyway.locations=classpath:db/migration

12
api/src/main/resources/application.properties

@ -0,0 +1,12 @@
server.port=8080
# datasource
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.username=${databasir.datasource.username}
spring.datasource.password=${databasir.datasource.password}
spring.datasource.url=jdbc:mysql://${databasir.datasource.url}/${databasir.datasource.database-name:databasir}
# jooq
spring.jooq.sql-dialect=mysql
# flyway
spring.flyway.enabled=true
spring.flyway.baseline-on-migrate=true
spring.flyway.locations=classpath:db/migration

30
build.gradle

@ -1,23 +1,38 @@
plugins {
id 'java'
id 'idea'
id 'io.spring.dependency-management' version '1.0.11.RELEASE'
id 'org.springframework.boot' version '2.5.8' apply false
id 'nu.studer.jooq' version '6.0.1' apply false
}
subprojects {
allprojects {
repositories {
mavenLocal()
mavenCentral()
}
}
subprojects {
apply plugin: 'java'
apply plugin: 'idea'
apply plugin: 'io.spring.dependency-management'
apply plugin: 'org.springframework.boot'
apply plugin: 'nu.studer.jooq'
group 'com.databasir'
version '1.0-SNAPSHOT'
repositories {
mavenCentral()
}
version '1.0.0'
ext {
lombokVersion = '1.18.22'
mapstructVersion = '1.4.2.Final'
junitVersion = '5.7.0'
slf4jVersion = '1.7.32'
jooqVersion = '3.15.5'
mysqlConnectorVersion = '8.0.27'
postgresqlConnectorVersion = '42.3.1'
hikariVersion = '5.0.0'
jacksonVersion = '2.13.1'
}
dependencies {
@ -28,10 +43,9 @@ subprojects {
annotationProcessor "org.projectlombok:lombok:${lombokVersion}"
implementation "org.mapstruct:mapstruct:${mapstructVersion}"
annotationProcessor "org.mapstruct:mapstruct:${mapstructVersion}"
annotationProcessor "org.mapstruct:mapstruct-processor:${mapstructVersion}"
implementation "org.slf4j:slf4j-api:${slf4jVersion}"
}
test {

3
common/build.gradle

@ -0,0 +1,3 @@
dependencies {
implementation 'org.apache.shiro:shiro-crypto-cipher:1.8.0'
}

8
common/src/main/java/com/databasir/common/DatabasirErrors.java

@ -0,0 +1,8 @@
package com.databasir.common;
public interface DatabasirErrors {
String getErrCode();
String getErrMessage();
}

49
common/src/main/java/com/databasir/common/DatabasirException.java

@ -0,0 +1,49 @@
package com.databasir.common;
import lombok.Getter;
import lombok.NoArgsConstructor;
@NoArgsConstructor
public class DatabasirException extends RuntimeException {
@Getter
private DatabasirErrors errorCodeMessage;
@Getter
private String errCode;
@Getter
private String errMessage;
/**
* @param errorCodeMessage 错误信息
*/
public DatabasirException(DatabasirErrors errorCodeMessage) {
super(errorCodeMessage.getErrMessage());
this.errorCodeMessage = errorCodeMessage;
this.errCode = errorCodeMessage.getErrCode();
this.errMessage = errorCodeMessage.getErrMessage();
}
/**
* @param errorCodeMessage 错误信息
* @param overrideMessage 覆盖 message
*/
public DatabasirException(DatabasirErrors errorCodeMessage, String overrideMessage) {
super(overrideMessage);
this.errorCodeMessage = errorCodeMessage;
this.errCode = errorCodeMessage.getErrCode();
this.errMessage = overrideMessage;
}
/**
* @param errorCodeMessage 错误信息
* @param cause root cause
*/
public DatabasirException(DatabasirErrors errorCodeMessage, Throwable cause) {
super(errorCodeMessage.getErrMessage(), cause);
this.errorCodeMessage = errorCodeMessage;
this.errCode = errorCodeMessage.getErrCode();
this.errMessage = errorCodeMessage.getErrMessage();
}
}

39
common/src/main/java/com/databasir/common/JsonData.java

@ -0,0 +1,39 @@
package com.databasir.common;
import lombok.Data;
@Data
public class JsonData<T> {
/**
* maybe null
*/
private T data;
/**
* only exists when error happened
*/
private String errCode;
/**
* only exists when error happened
*/
private String errMessage;
public static <T> JsonData<T> ok() {
return ok(null);
}
public static <T> JsonData<T> ok(T data) {
JsonData<T> jsonData = new JsonData<>();
jsonData.setData(data);
return jsonData;
}
public static <T> JsonData<T> error(String errorCode, String errMessage) {
JsonData<T> jsonData = new JsonData<>();
jsonData.setErrCode(errorCode);
jsonData.setErrMessage(errMessage);
return jsonData;
}
}

27
common/src/main/java/com/databasir/common/SystemException.java

@ -0,0 +1,27 @@
package com.databasir.common;
public class SystemException extends RuntimeException{
private static final String MSG_INTERNAL_SERVER_ERROR = "服务器开小差了,请稍后再试";
public static final SystemException INTERNAL_SERVER_ERROR = new SystemException(MSG_INTERNAL_SERVER_ERROR);
/**
* @param msg the detail message
*/
public SystemException(String msg) {
super(msg);
}
/**
* @param msg the detail message
* @param cause the nested exception
*/
public SystemException(String msg, Throwable cause) {
super(msg, cause);
}
public static SystemException internalServerErrorWithCause(Throwable cause) {
return new SystemException(MSG_INTERNAL_SERVER_ERROR, cause);
}
}

36
common/src/main/java/com/databasir/common/codec/Aes.java

@ -0,0 +1,36 @@
package com.databasir.common.codec;
import org.apache.shiro.crypto.AesCipherService;
import java.nio.charset.StandardCharsets;
import java.security.Key;
import java.util.Base64;
public class Aes {
public static String encryptToBase64Data(String data, String key) {
return new AesCipherService()
.encrypt(toBytes(data), toBytes(key))
.toBase64();
}
public static String decryptFromBase64Data(String encryptData, String key) {
byte[] originEncrypted = Base64.getDecoder().decode(toBytes(encryptData));
byte[] originKey = toBytes(key);
return toString(new AesCipherService().decrypt(originEncrypted, originKey).getBytes());
}
public static String randomBase64Key() {
Key key = new AesCipherService().generateNewKey();
return Base64.getEncoder().encodeToString(key.getEncoded());
}
private static byte[] toBytes(String data) {
return data.getBytes(StandardCharsets.UTF_8);
}
private static String toString(byte[] bytes) {
return new String(bytes, StandardCharsets.UTF_8);
}
}

72
common/src/main/java/com/databasir/common/codec/Rsa.java

@ -0,0 +1,72 @@
package com.databasir.common.codec;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import javax.crypto.Cipher;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.spec.EncodedKeySpec;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
public class Rsa {
private static final String RSA = "RSA";
@SneakyThrows
public static String encryptToBase64DataByPublicKey(String data, String publicBase64Key) {
Cipher encryptCipher = Cipher.getInstance(RSA);
encryptCipher.init(Cipher.ENCRYPT_MODE, decodeBase64PublicKey(publicBase64Key));
byte[] dataBytes = data.getBytes(StandardCharsets.UTF_8);
byte[] encryptedDataBytes = encryptCipher.doFinal(dataBytes);
return Base64.getEncoder().encodeToString(encryptedDataBytes);
}
@SneakyThrows
public static String decryptFromBase64DataByPrivateKey(String data, String privateKey) {
Cipher decryptCipher = Cipher.getInstance(RSA);
decryptCipher.init(Cipher.DECRYPT_MODE, decodeBase64PrivateKey(privateKey));
byte[] dataBytes = Base64.getDecoder().decode(data);
byte[] decryptedBytes = decryptCipher.doFinal(dataBytes);
return new String(decryptedBytes, StandardCharsets.UTF_8);
}
@SneakyThrows
private static PublicKey decodeBase64PublicKey(String base64PublicKey) {
byte[] keyBytes = Base64.getDecoder().decode(base64PublicKey);
KeyFactory keyFactory = KeyFactory.getInstance(RSA);
EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(keyBytes);
return keyFactory.generatePublic(publicKeySpec);
}
@SneakyThrows
private static PrivateKey decodeBase64PrivateKey(String base64PrivateKey) {
byte[] keyBytes = Base64.getDecoder().decode(base64PrivateKey);
KeyFactory keyFactory = KeyFactory.getInstance(RSA);
EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(keyBytes);
return keyFactory.generatePrivate(privateKeySpec);
}
@SneakyThrows
public static RsaBase64Key generateBase64Key() {
KeyPairGenerator generator = KeyPairGenerator.getInstance(RSA);
generator.initialize(2048);
KeyPair keyPair = generator.generateKeyPair();
String privateKey = Base64.getEncoder().encodeToString(keyPair.getPrivate().getEncoded());
String publicKey = Base64.getEncoder().encodeToString(keyPair.getPublic().getEncoded());
return new RsaBase64Key(privateKey, publicKey);
}
@Getter
@RequiredArgsConstructor
public static class RsaBase64Key {
private final String privateBase64Key;
private final String publicBase64Key;
}
}

18
common/src/main/java/com/databasir/common/codec/Sha.java

@ -0,0 +1,18 @@
package com.databasir.common.codec;
import lombok.SneakyThrows;
import org.apache.shiro.codec.Hex;
import java.security.MessageDigest;
public class Sha {
@SneakyThrows
public static String sha256(String data) {
MessageDigest messageDigest = MessageDigest.getInstance("SHA-256");
messageDigest.update(data.getBytes());
byte[] bytes = messageDigest.digest();
return Hex.encodeToString(bytes);
}
}

4
common/src/main/java/com/databasir/common/exception/Forbidden.java

@ -0,0 +1,4 @@
package com.databasir.common.exception;
public class Forbidden extends RuntimeException{
}

24
common/src/main/java/com/databasir/common/exception/InvalidTokenException.java

@ -0,0 +1,24 @@
package com.databasir.common.exception;
import com.databasir.common.DatabasirErrors;
import com.databasir.common.DatabasirException;
public class InvalidTokenException extends DatabasirException {
public InvalidTokenException(DatabasirErrors errorCodeMessage) {
super(errorCodeMessage);
}
public InvalidTokenException(DatabasirErrors errorCodeMessage, String overrideMessage) {
super(errorCodeMessage, overrideMessage);
}
public InvalidTokenException(DatabasirErrors errorCodeMessage, Throwable cause) {
super(errorCodeMessage, cause);
}
@Override
public String toString() {
return getErrCode() + ": " + getErrMessage();
}
}

35
common/src/test/java/com/databasir/common/codec/CodecTest.java

@ -0,0 +1,35 @@
package com.databasir.common.codec;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
public class CodecTest {
@Test
public void testAes() {
String key = Aes.randomBase64Key();
Assertions.assertNotNull(key);
String data = "hello world!";
String encryptedData = Aes.encryptToBase64Data(data, key);
Assertions.assertNotNull(encryptedData);
String decryptedData = Aes.decryptFromBase64Data(encryptedData, key);
Assertions.assertEquals(data, decryptedData);
}
@Test
public void testRsa() {
Rsa.RsaBase64Key key = Rsa.generateBase64Key();
Assertions.assertNotNull(key);
Assertions.assertNotNull(key.getPrivateBase64Key());
Assertions.assertNotNull(key.getPublicBase64Key());
String data = "Hello world!";
String encrypted = Rsa.encryptToBase64DataByPublicKey(data, key.getPublicBase64Key());
Assertions.assertNotNull(encrypted);
String decrypted = Rsa.decryptFromBase64DataByPrivateKey(encrypted, key.getPrivateBase64Key());
Assertions.assertEquals(data, decrypted);
}
}

34
core/build.gradle

@ -1,5 +1,37 @@
plugins {
id 'io.spring.dependency-management'
id 'org.springframework.boot' apply false
}
dependencies {
testImplementation 'mysql:mysql-connector-java:8.0.27'
// internal module
implementation project(":common")
implementation project(":dao")
implementation project(":plugin")
// spring boot
implementation 'org.springframework.boot:spring-boot-starter-data-jdbc'
implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation 'org.springframework.boot:spring-boot-starter-jooq'
implementation 'org.springframework.boot:spring-boot-starter-mail'
implementation 'org.springframework.boot:spring-boot-starter-security'
// datasource
implementation "com.zaxxer:HikariCP:${hikariVersion}"
// jdbc driver
implementation "mysql:mysql-connector-java:${mysqlConnectorVersion}"
implementation "org.postgresql:postgresql:${postgresqlConnectorVersion}"
// jackson
implementation "com.fasterxml.jackson.core:jackson-core:${jacksonVersion}"
implementation "com.fasterxml.jackson.core:jackson-databind:${jacksonVersion}"
// others
implementation 'com.auth0:java-jwt:3.18.3'
implementation 'org.commonmark:commonmark:0.18.1'
implementation 'org.freemarker:freemarker:2.3.31'
// test
testImplementation "mysql:mysql-connector-java:${mysqlConnectorVersion}"
}

38
core/src/main/java/com/databasir/core/domain/DomainErrors.java

@ -0,0 +1,38 @@
package com.databasir.core.domain;
import com.databasir.common.DatabasirErrors;
import com.databasir.common.DatabasirException;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor
@Getter
public enum DomainErrors implements DatabasirErrors {
REFRESH_TOKEN_EXPIRED("X_0001", "refresh token expired"),
ACCESS_TOKEN_REFRESH_INVALID("X_0002", "invalid refresh token operation"),
NOT_SUPPORT_DATABASE_TYPE("A_10000", "不支持的数据库类型, 请检查项目配置"),
PROJECT_NOT_FOUND("A_10001", "项目不存在"),
DATABASE_META_NOT_FOUND("A_10002", "获取数据库信息失败"),
CONNECT_DATABASE_FAILED("A_10003", "连接数据库失败,请检查连接配置"),
GROUP_OWNER_MUST_NOT_BE_EMPTY("A_10004", "请至少指定一个分组组长"),
PASSWORD_MUST_NOT_BE_BLANK("A_10005", "密码不能为空"),
USERNAME_OR_EMAIL_DUPLICATE("A_10006", "用户名或邮箱已存在"),
USER_ROLE_DUPLICATE("A_10007", "用户角色已存在"),
PROJECT_NAME_DUPLICATE("A_10008", "项目名称已被占用"),
CANNOT_UPDATE_SELF_ROLE("A_10009", "无法对自己执行角色变更的操作"),
UPDATE_PASSWORD_CONFIRM_FAILED("A_10010", "两次密码输入不一致"),
ORIGIN_PASSWORD_NOT_CORRECT("A_10011", "原密码不正确");
private final String errCode;
private final String errMessage;
public DatabasirException exception() {
return new DatabasirException(this);
}
public DatabasirException exception(Throwable origin) {
return new DatabasirException(this, origin);
}
}

9
core/src/main/java/com/databasir/core/domain/document/converter/BaseConverter.java

@ -0,0 +1,9 @@
package com.databasir.core.domain.document.converter;
public interface BaseConverter {
@NullToEmpty
default String nullToEmpty(String s) {
return s == null ? "" : s;
}
}

20
core/src/main/java/com/databasir/core/domain/document/converter/DocumentHistoryPojoConverter.java

@ -0,0 +1,20 @@
package com.databasir.core.domain.document.converter;
import com.databasir.core.domain.document.data.DatabaseDocumentResponse;
import com.databasir.core.infrastructure.converter.JsonConverter;
import com.databasir.dao.tables.pojos.DatabaseDocumentHistoryPojo;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.ReportingPolicy;
@Mapper(componentModel = "spring", uses = JsonConverter.class, unmappedTargetPolicy = ReportingPolicy.IGNORE)
public interface DocumentHistoryPojoConverter {
@Mapping(target = "databaseDocumentObject", source = "databaseMetaObject")
@Mapping(target = "id", ignore = true)
@Mapping(target = "createAt", ignore = true)
DatabaseDocumentHistoryPojo of(DatabaseDocumentResponse databaseMetaObject,
Integer projectId,
Integer databaseDocumentId,
Long version);
}

74
core/src/main/java/com/databasir/core/domain/document/converter/DocumentPojoConverter.java

@ -0,0 +1,74 @@
package com.databasir.core.domain.document.converter;
import com.databasir.core.infrastructure.converter.JsonConverter;
import com.databasir.core.meta.data.ColumnMeta;
import com.databasir.core.meta.data.IndexMeta;
import com.databasir.core.meta.data.TriggerMeta;
import com.databasir.dao.tables.pojos.*;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.ReportingPolicy;
import java.util.List;
import java.util.stream.Collectors;
@Mapper(componentModel = "spring", uses = JsonConverter.class, unmappedTargetPolicy = ReportingPolicy.WARN)
public interface DocumentPojoConverter extends BaseConverter {
@Mapping(target = "databaseName", source = "meta.databaseName")
DatabaseDocumentPojo toDatabasePojo(Integer projectId,
com.databasir.core.meta.data.DatabaseMeta meta,
Long version);
@Mapping(target = "databaseName", source = "meta.databaseName")
DatabaseDocumentPojo toDatabasePojo(Integer projectId,
com.databasir.core.meta.data.DatabaseMeta meta,
Integer id,
Long version);
@Mapping(target = "comment", qualifiedBy = NullToEmpty.class)
TableDocumentPojo toTablePojo(Integer databaseDocumentId,
com.databasir.core.meta.data.TableMeta meta);
default List<TableColumnDocumentPojo> toColumnPojo(Integer databaseDocumentId,
Integer tableDocumentId,
List<ColumnMeta> metaList) {
return metaList.stream()
.map(meta -> toColumnPojo(databaseDocumentId, tableDocumentId, meta))
.collect(Collectors.toList());
}
@Mapping(target = "comment", qualifiedBy = NullToEmpty.class)
TableColumnDocumentPojo toColumnPojo(Integer databaseDocumentId,
Integer tableDocumentId,
ColumnMeta meta);
default List<TableIndexDocumentPojo> toIndexPojo(Integer databaseDocumentId,
Integer tableDocumentId,
List<IndexMeta> metaList) {
return metaList.stream()
.map(meta -> toIndexPojo(databaseDocumentId, tableDocumentId, meta))
.collect(Collectors.toList());
}
@Mapping(target = "isPrimary", source = "meta.isPrimaryKey")
@Mapping(target = "isUnique", source = "meta.isUniqueKey")
@Mapping(target = "columnNameArray", source = "meta.columnNames")
TableIndexDocumentPojo toIndexPojo(Integer databaseDocumentId,
Integer tableDocumentId,
IndexMeta meta);
default List<TableTriggerDocumentPojo> toTriggerPojo(Integer databaseDocumentId,
Integer tableDocumentId,
List<TriggerMeta> metaList) {
return metaList.stream()
.map(meta -> toTriggerPojo(databaseDocumentId, tableDocumentId, meta))
.collect(Collectors.toList());
}
@Mapping(target = "triggerCreateAt", source = "meta.createAt")
TableTriggerDocumentPojo toTriggerPojo(Integer databaseDocumentId,
Integer tableDocumentId,
TriggerMeta meta);
}

31
core/src/main/java/com/databasir/core/domain/document/converter/DocumentResponseConverter.java

@ -0,0 +1,31 @@
package com.databasir.core.domain.document.converter;
import com.databasir.core.domain.document.data.DatabaseDocumentResponse;
import com.databasir.core.infrastructure.converter.JsonConverter;
import com.databasir.dao.tables.pojos.*;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.ReportingPolicy;
import java.util.List;
@Mapper(componentModel = "spring", uses = JsonConverter.class, unmappedTargetPolicy = ReportingPolicy.WARN)
public interface DocumentResponseConverter {
@Mapping(target = "columns", source = "columns")
@Mapping(target = "indexes", source = "indexes")
@Mapping(target = "triggers", source = "triggers")
DatabaseDocumentResponse.TableDocumentResponse of(TableDocumentPojo tableDocument,
List<TableColumnDocumentPojo> columns,
List<TableIndexDocumentPojo> indexes,
List<TableTriggerDocumentPojo> triggers);
@Mapping(target = "columnNames", source = "columnNameArray")
DatabaseDocumentResponse.TableDocumentResponse.IndexDocumentResponse of(TableIndexDocumentPojo indexDocument);
@Mapping(target = "id", source = "databaseDocument.id")
@Mapping(target = "createAt", source = "databaseDocument.createAt")
@Mapping(target = "documentVersion", source = "databaseDocument.version")
DatabaseDocumentResponse of(DatabaseDocumentPojo databaseDocument,
List<DatabaseDocumentResponse.TableDocumentResponse> tables);
}

14
core/src/main/java/com/databasir/core/domain/document/converter/NullToEmpty.java

@ -0,0 +1,14 @@
package com.databasir.core.domain.document.converter;
import org.mapstruct.Qualifier;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Qualifier
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.CLASS)
public @interface NullToEmpty {
}

125
core/src/main/java/com/databasir/core/domain/document/data/DatabaseDocumentResponse.java

@ -0,0 +1,125 @@
package com.databasir.core.domain.document.data;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class DatabaseDocumentResponse {
private Integer id;
private String databaseName;
private String productName;
private String productVersion;
private Integer documentVersion;
@Builder.Default
private List<TableDocumentResponse> tables = new ArrayList<>();
private LocalDateTime createAt;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public static class TableDocumentResponse {
private Integer id;
private String name;
private String type;
private String comment;
@Builder.Default
private List<ColumnDocumentResponse> columns = new ArrayList<>();
@Builder.Default
private List<IndexDocumentResponse> indexes = new ArrayList<>();
@Builder.Default
private List<TriggerDocumentResponse> triggers = new ArrayList<>();
private LocalDateTime createAt;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public static class ColumnDocumentResponse {
private Integer id;
private String name;
private String type;
private Integer size;
private Integer decimalDigits;
private String comment;
private String nullable;
private String autoIncrement;
private String defaultValue;
private LocalDateTime createAt;
}
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public static class IndexDocumentResponse {
private Integer id;
private String name;
private Boolean isPrimary;
private Boolean isUnique;
@Builder.Default
private List<String> columnNames = new ArrayList<>();
private LocalDateTime createAt;
}
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public static class TriggerDocumentResponse {
private Integer id;
private String name;
private String timing;
private String manipulation;
private String statement;
private String triggerCreateAt;
private LocalDateTime createAt;
}
}
}

20
core/src/main/java/com/databasir/core/domain/document/data/DatabaseDocumentVersionResponse.java

@ -0,0 +1,20 @@
package com.databasir.core.domain.document.data;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class DatabaseDocumentVersionResponse {
private Long version;
private LocalDateTime createAt;
}

186
core/src/main/java/com/databasir/core/domain/document/service/DocumentService.java

@ -0,0 +1,186 @@
package com.databasir.core.domain.document.service;
import com.databasir.core.Databasir;
import com.databasir.core.DatabasirConfig;
import com.databasir.core.domain.DomainErrors;
import com.databasir.core.domain.document.converter.DocumentHistoryPojoConverter;
import com.databasir.core.domain.document.converter.DocumentPojoConverter;
import com.databasir.core.domain.document.converter.DocumentResponseConverter;
import com.databasir.core.domain.document.data.DatabaseDocumentResponse;
import com.databasir.core.domain.document.data.DatabaseDocumentVersionResponse;
import com.databasir.core.infrastructure.connection.DatabaseConnectionService;
import com.databasir.core.infrastructure.converter.JsonConverter;
import com.databasir.core.meta.data.DatabaseMeta;
import com.databasir.dao.impl.*;
import com.databasir.dao.tables.pojos.*;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.sql.Connection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
@Service
@RequiredArgsConstructor
@Slf4j
public class DocumentService {
private final ProjectDao projectDao;
private final ProjectSyncRuleDao projectSyncRuleDao;
private final DataSourceDao dataSourceDao;
private final DataSourcePropertyDao dataSourcePropertyDao;
private final SysKeyDao sysKeyDao;
private final DatabaseConnectionService databaseConnectionService;
private final DatabaseDocumentDao databaseDocumentDao;
private final TableDocumentDao tableDocumentDao;
private final TableColumnDocumentDao tableColumnDocumentDao;
private final TableIndexDocumentDao tableIndexDocumentDao;
private final TableTriggerDocumentDao tableTriggerDocumentDao;
private final DatabaseDocumentHistoryDao databaseDocumentHistoryDao;
private final DocumentPojoConverter documentPojoConverter;
private final DocumentResponseConverter documentResponseConverter;
private final DocumentHistoryPojoConverter documentHistoryPojoConverter;
private final JsonConverter jsonConverter;
@Transactional
public void syncByProjectId(Integer projectId) {
ProjectPojo project = projectDao.selectOptionalById(projectId)
.orElseThrow(DomainErrors.PROJECT_NOT_FOUND::exception);
DatabaseMeta meta = retrieveDatabaseMeta(projectId);
Optional<DatabaseDocumentPojo> historyDocumentOpt = databaseDocumentDao.selectOptionalByProjectId(projectId);
if (historyDocumentOpt.isPresent()) {
DatabaseDocumentPojo historyDocument = historyDocumentOpt.get();
Integer previousDocumentId = historyDocument.getId();
saveAsHistory(historyDocument);
deleteDeprecatedDocument(previousDocumentId);
saveNewDocument(meta, historyDocument.getVersion() + 1, historyDocument.getProjectId(), previousDocumentId);
} else {
saveNewDocument(meta, 1L, projectId, null);
}
}
private DatabaseMeta retrieveDatabaseMeta(Integer projectId) {
ProjectSyncRulePojo rule = projectSyncRuleDao.selectByProjectId(projectId);
DataSourcePojo dataSource = dataSourceDao.selectByProjectId(projectId);
List<DataSourcePropertyPojo> properties = dataSourcePropertyDao.selectByDataSourceId(dataSource.getId());
Connection jdbcConnection = databaseConnectionService.create(dataSource, properties);
DatabasirConfig databasirConfig = new DatabasirConfig();
databasirConfig.setIgnoreTableNameRegex(jsonConverter.fromJson(rule.getIgnoreTableNameRegexArray()));
databasirConfig.setIgnoreTableColumnNameRegex(jsonConverter.fromJson(rule.getIgnoreColumnNameRegexArray()));
return Databasir.of(databasirConfig)
.get(jdbcConnection, dataSource.getDatabaseName())
.orElseThrow(DomainErrors.DATABASE_META_NOT_FOUND::exception);
}
private void saveAsHistory(DatabaseDocumentPojo databaseDocument) {
// save history
Integer projectId = databaseDocument.getProjectId();
Integer databaseMetaId = databaseDocument.getId();
DatabaseDocumentResponse databaseDocumentResponse = getOneByProjectId(projectId, null).orElse(null);
Long currVersion = databaseDocument.getVersion();
DatabaseDocumentHistoryPojo documentHistoryPojo =
documentHistoryPojoConverter.of(databaseDocumentResponse, projectId, databaseMetaId, currVersion);
databaseDocumentHistoryDao.insertAndReturnId(documentHistoryPojo);
log.info("save old meta info to history success");
}
private void deleteDeprecatedDocument(Integer databaseDocumentId) {
// delete old meta info
tableDocumentDao.deleteByDatabaseDocumentId(databaseDocumentId);
tableColumnDocumentDao.deleteByDatabaseDocumentId(databaseDocumentId);
tableIndexDocumentDao.deleteByDatabaseMetaId(databaseDocumentId);
tableTriggerDocumentDao.deleteByDatabaseDocumentId(databaseDocumentId);
log.info("delete old meta info success");
}
private void saveNewDocument(DatabaseMeta meta,
Long version,
Integer projectId,
Integer databaseDocumentId) {
Integer currentDatabaseDocumentId = databaseDocumentId;
if (databaseDocumentId == null) {
currentDatabaseDocumentId =
databaseDocumentDao.insertAndReturnId(documentPojoConverter.toDatabasePojo(projectId, meta, 1L));
} else {
databaseDocumentDao.update(documentPojoConverter.toDatabasePojo(projectId, meta, databaseDocumentId, version));
}
final Integer docId = currentDatabaseDocumentId;
meta.getTables().forEach(table -> {
TableDocumentPojo tableMeta =
documentPojoConverter.toTablePojo(docId, table);
Integer tableMetaId = tableDocumentDao.insertAndReturnId(tableMeta);
List<TableColumnDocumentPojo> tableColumnMetas = documentPojoConverter.toColumnPojo(docId, tableMetaId, table.getColumns());
tableColumnDocumentDao.batchInsert(tableColumnMetas);
List<TableIndexDocumentPojo> tableIndexMetas = documentPojoConverter.toIndexPojo(docId, tableMetaId, table.getIndexes());
tableIndexDocumentDao.batchInsert(tableIndexMetas);
List<TableTriggerDocumentPojo> tableTriggerMetas = documentPojoConverter.toTriggerPojo(docId, tableMetaId, table.getTriggers());
tableTriggerDocumentDao.batchInsert(tableTriggerMetas);
});
log.info("save new meta info success");
}
public Optional<DatabaseDocumentResponse> getOneByProjectId(Integer projectId, Long version) {
if (version == null) {
return databaseDocumentDao.selectOptionalByProjectId(projectId)
.map(document -> {
Integer id = document.getId();
List<TableDocumentPojo> tables = tableDocumentDao.selectByDatabaseDocumentId(id);
List<TableColumnDocumentPojo> columns = tableColumnDocumentDao.selectByDatabaseDocumentId(id);
List<TableIndexDocumentPojo> indexes = tableIndexDocumentDao.selectByDatabaseMetaId(id);
List<TableTriggerDocumentPojo> triggers = tableTriggerDocumentDao.selectByDatabaseDocumentId(id);
Map<Integer, List<TableColumnDocumentPojo>> columnsGroupByTableMetaId = columns.stream()
.collect(Collectors.groupingBy(TableColumnDocumentPojo::getTableDocumentId));
Map<Integer, List<TableIndexDocumentPojo>> indexesGroupByTableMetaId = indexes.stream()
.collect(Collectors.groupingBy(TableIndexDocumentPojo::getTableDocumentId));
Map<Integer, List<TableTriggerDocumentPojo>> triggersGroupByTableMetaId = triggers.stream()
.collect(Collectors.groupingBy(TableTriggerDocumentPojo::getTableDocumentId));
List<DatabaseDocumentResponse.TableDocumentResponse> tableDocumentResponseList = tables.stream()
.map(table -> {
List<TableColumnDocumentPojo> subColumns = columnsGroupByTableMetaId.getOrDefault(table.getId(), Collections.emptyList());
List<TableIndexDocumentPojo> subIndexes = indexesGroupByTableMetaId.getOrDefault(table.getId(), Collections.emptyList());
List<TableTriggerDocumentPojo> subTriggers = triggersGroupByTableMetaId.getOrDefault(table.getId(), Collections.emptyList());
return documentResponseConverter.of(table, subColumns, subIndexes, subTriggers);
})
.collect(Collectors.toList());
return documentResponseConverter.of(document, tableDocumentResponseList);
});
} else {
return databaseDocumentHistoryDao.selectOptionalByProjectIdAndVersion(projectId, version)
.map(obj -> jsonConverter.of(obj.getDatabaseDocumentObject()));
}
}
public Page<DatabaseDocumentVersionResponse> getVersionsBySchemaSourceId(Integer projectId, Pageable page) {
return databaseDocumentDao.selectOptionalByProjectId(projectId)
.map(schemaMeta -> databaseDocumentHistoryDao.selectPageByDatabaseDocumentId(page, schemaMeta.getId())
.map(history -> DatabaseDocumentVersionResponse.builder()
.version(history.getVersion())
.createAt(history.getCreateAt())
.build()))
.orElseGet(Page::empty);
}
}

14
core/src/main/java/com/databasir/core/domain/group/converter/GroupPojoConverter.java

@ -0,0 +1,14 @@
package com.databasir.core.domain.group.converter;
import com.databasir.core.domain.group.data.GroupCreateRequest;
import com.databasir.core.domain.group.data.GroupUpdateRequest;
import com.databasir.dao.tables.pojos.GroupPojo;
import org.mapstruct.Mapper;
@Mapper(componentModel = "spring")
public interface GroupPojoConverter {
GroupPojo of(GroupCreateRequest groupCreateRequest);
GroupPojo of(GroupUpdateRequest groupUpdateRequest);
}

39
core/src/main/java/com/databasir/core/domain/group/converter/GroupResponseConverter.java

@ -0,0 +1,39 @@
package com.databasir.core.domain.group.converter;
import com.databasir.core.domain.group.data.GroupMemberPageResponse;
import com.databasir.core.domain.group.data.GroupPageResponse;
import com.databasir.core.domain.group.data.GroupResponse;
import com.databasir.dao.tables.pojos.GroupPojo;
import com.databasir.dao.tables.pojos.UserPojo;
import com.databasir.dao.value.GroupMemberDetailPojo;
import org.mapstruct.Mapper;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@Mapper(componentModel = "spring")
public interface GroupResponseConverter {
default List<GroupPageResponse> toResponse(List<GroupPojo> pojos,
Map<Integer, List<String>> groupOwnerGroupByGroupId,
Map<Integer, Integer> projectCountMapByGroupId) {
return pojos.stream()
.map(group -> {
Integer groupId = group.getId();
List<String> owners = groupOwnerGroupByGroupId.getOrDefault(groupId, new ArrayList<>());
Integer projectCount = projectCountMapByGroupId.getOrDefault(groupId, 0);
return toResponse(group, owners, projectCount);
})
.collect(Collectors.toList());
}
GroupPageResponse toResponse(GroupPojo groupPojo,
List<String> groupOwnerNames,
Integer projectCount);
GroupResponse toResponse(GroupPojo groupPojo, List<UserPojo> groupOwners);
GroupMemberPageResponse toResponse(GroupMemberDetailPojo data);
}

24
core/src/main/java/com/databasir/core/domain/group/data/GroupCreateRequest.java

@ -0,0 +1,24 @@
package com.databasir.core.domain.group.data;
import lombok.Data;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.Size;
import java.util.ArrayList;
import java.util.List;
@Data
public class GroupCreateRequest {
@NotBlank
private String name;
@NotBlank
private String description;
@NotEmpty
@Size(min = 1, max = 20, message = "一个分组的组长最多为 20 人,最少为 1 人")
private List<Integer> groupOwnerUserIds = new ArrayList<>();
}

12
core/src/main/java/com/databasir/core/domain/group/data/GroupMemberCreateRequest.java

@ -0,0 +1,12 @@
package com.databasir.core.domain.group.data;
import lombok.Data;
@Data
public class GroupMemberCreateRequest {
private Integer userId;
private String role;
}

32
core/src/main/java/com/databasir/core/domain/group/data/GroupMemberPageCondition.java

@ -0,0 +1,32 @@
package com.databasir.core.domain.group.data;
import lombok.Data;
import org.jooq.Condition;
import org.jooq.impl.DSL;
import java.util.ArrayList;
import java.util.List;
import static com.databasir.dao.Tables.USER;
import static com.databasir.dao.Tables.USER_ROLE;
@Data
public class GroupMemberPageCondition {
private String role;
private String nicknameOrUsernameOrEmailContains;
public Condition toCondition() {
List<Condition> conditions = new ArrayList<>();
if (role != null) {
conditions.add(USER_ROLE.ROLE.eq(role));
}
if (nicknameOrUsernameOrEmailContains != null) {
conditions.add(USER.USERNAME.contains(nicknameOrUsernameOrEmailContains)
.or(USER.NICKNAME.contains(nicknameOrUsernameOrEmailContains))
.or(USER.EMAIL.contains(nicknameOrUsernameOrEmailContains)));
}
return conditions.stream().reduce(Condition::and).orElse(DSL.trueCondition());
}
}

21
core/src/main/java/com/databasir/core/domain/group/data/GroupMemberPageResponse.java

@ -0,0 +1,21 @@
package com.databasir.core.domain.group.data;
import lombok.Data;
import java.time.LocalDateTime;
@Data
public class GroupMemberPageResponse {
private Integer userId;
private String role;
private String username;
private String nickname;
private String email;
private LocalDateTime createAt;
}

14
core/src/main/java/com/databasir/core/domain/group/data/GroupMemberRoleUpdateRequest.java

@ -0,0 +1,14 @@
package com.databasir.core.domain.group.data;
import lombok.Data;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Pattern;
@Data
public class GroupMemberRoleUpdateRequest {
@NotBlank
private String role;
}

19
core/src/main/java/com/databasir/core/domain/group/data/GroupPageCondition.java

@ -0,0 +1,19 @@
package com.databasir.core.domain.group.data;
import com.databasir.dao.Tables;
import lombok.Data;
import org.jooq.Condition;
import org.jooq.impl.DSL;
@Data
public class GroupPageCondition {
private String groupNameContains;
public Condition toCondition() {
if (groupNameContains != null) {
return Tables.GROUP.NAME.contains(groupNameContains);
}
return DSL.trueCondition();
}
}

20
core/src/main/java/com/databasir/core/domain/group/data/GroupPageResponse.java

@ -0,0 +1,20 @@
package com.databasir.core.domain.group.data;
import lombok.Data;
import java.util.List;
@Data
public class GroupPageResponse {
private Integer id;
private String name;
private String description;
private List<String> groupOwnerNames;
private Integer projectCount;
}

34
core/src/main/java/com/databasir/core/domain/group/data/GroupResponse.java

@ -0,0 +1,34 @@
package com.databasir.core.domain.group.data;
import lombok.Data;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
@Data
public class GroupResponse {
private Integer id;
private String name;
private String description;
private List<GroupOwnerResponse> groupOwners = new ArrayList<>();
private LocalDateTime updateAt;
private LocalDateTime createAt;
@Data
public static class GroupOwnerResponse {
private Integer id;
private String nickname;
private String email;
}
}

26
core/src/main/java/com/databasir/core/domain/group/data/GroupUpdateRequest.java

@ -0,0 +1,26 @@
package com.databasir.core.domain.group.data;
import lombok.Data;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.util.ArrayList;
import java.util.List;
@Data
public class GroupUpdateRequest {
@NotNull
private Integer id;
@NotBlank
private String name;
@NotBlank
private String description;
@NotEmpty
private List<Integer> groupOwnerUserIds = new ArrayList<>();
}

146
core/src/main/java/com/databasir/core/domain/group/service/GroupService.java

@ -0,0 +1,146 @@
package com.databasir.core.domain.group.service;
import com.databasir.core.domain.DomainErrors;
import com.databasir.core.domain.group.converter.GroupPojoConverter;
import com.databasir.core.domain.group.converter.GroupResponseConverter;
import com.databasir.core.domain.group.data.*;
import com.databasir.dao.impl.GroupDao;
import com.databasir.dao.impl.ProjectDao;
import com.databasir.dao.impl.UserDao;
import com.databasir.dao.impl.UserRoleDao;
import com.databasir.dao.tables.pojos.GroupPojo;
import com.databasir.dao.tables.pojos.UserPojo;
import com.databasir.dao.tables.pojos.UserRolePojo;
import com.databasir.dao.value.GroupMemberSimplePojo;
import com.databasir.dao.value.GroupProjectCountPojo;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@Service
@RequiredArgsConstructor
public class GroupService {
private final GroupDao groupDao;
private final UserDao userDao;
private final UserRoleDao userRoleDao;
private final ProjectDao projectDao;
private final GroupPojoConverter groupPojoConverter;
private final GroupResponseConverter groupResponseConverter;
@Transactional
public void create(GroupCreateRequest request) {
GroupPojo groupPojo = groupPojoConverter.of(request);
Integer groupId = groupDao.insertAndReturnId(groupPojo);
List<UserRolePojo> roles = request.getGroupOwnerUserIds()
.stream()
.map(userId -> {
UserRolePojo role = new UserRolePojo();
role.setUserId(userId);
role.setRole("GROUP_OWNER");
role.setGroupId(groupId);
return role;
})
.collect(Collectors.toList());
userRoleDao.batchInsert(roles);
}
@Transactional
public void update(GroupUpdateRequest request) {
GroupPojo groupPojo = groupPojoConverter.of(request);
groupDao.updateById(groupPojo);
userRoleDao.deleteByRoleAndGroupId("GROUP_OWNER", groupPojo.getId());
List<UserRolePojo> roles = request.getGroupOwnerUserIds()
.stream()
.map(userId -> {
UserRolePojo role = new UserRolePojo();
role.setUserId(userId);
role.setRole("GROUP_OWNER");
role.setGroupId(groupPojo.getId());
return role;
})
.collect(Collectors.toList());
userRoleDao.batchInsert(roles);
}
public void delete(Integer groupId) {
groupDao.deleteById(groupId);
userRoleDao.deleteByGroupId(groupId);
}
public Page<GroupPageResponse> list(Pageable pageable, GroupPageCondition condition) {
Page<GroupPojo> page = groupDao.selectByPage(pageable, condition.toCondition());
List<Integer> groupIdList = page.getContent()
.stream()
.map(GroupPojo::getId)
.collect(Collectors.toList());
Map<Integer, List<GroupMemberSimplePojo>> ownersGroupByGroupId = userRoleDao.selectOwnerNamesByGroupIdIn(groupIdList)
.stream()
.collect(Collectors.groupingBy(GroupMemberSimplePojo::getGroupId));
Map<Integer, GroupProjectCountPojo> projectCountMapByGroupId = projectDao.selectCountByGroupIds(groupIdList)
.stream()
.collect(Collectors.toMap(GroupProjectCountPojo::getGroupId, v -> v));
return page.map(groupPojo -> {
Integer groupId = groupPojo.getId();
List<String> owners = ownersGroupByGroupId.getOrDefault(groupId, new ArrayList<>())
.stream()
.map(GroupMemberSimplePojo::getNickname)
.collect(Collectors.toList());
GroupProjectCountPojo countPojo = projectCountMapByGroupId.get(groupId);
Integer projectCount = countPojo == null ? 0 : countPojo.getCount();
return groupResponseConverter.toResponse(groupPojo, owners, projectCount);
});
}
public Page<GroupMemberPageResponse> listGroupMembers(Integer groupId,
Pageable pageable,
GroupMemberPageCondition condition) {
return userDao.selectGroupMembers(groupId, pageable, condition.toCondition())
.map(groupResponseConverter::toResponse);
}
public GroupResponse get(Integer groupId) {
GroupPojo groupPojo = groupDao.selectById(groupId);
List<UserPojo> users = userDao.selectLimitUsersByRoleAndGroup(groupId, "GROUP_OWNER", 50);
return groupResponseConverter.toResponse(groupPojo, users);
}
public void removeMember(Integer groupId, Integer userId) {
userRoleDao.deleteByUserIdAndGroupId(userId, groupId);
}
public void addMember(Integer groupId, GroupMemberCreateRequest request) {
if (userRoleDao.hasRole(request.getUserId(), groupId)) {
throw DomainErrors.USER_ROLE_DUPLICATE.exception();
}
UserRolePojo pojo = new UserRolePojo();
pojo.setGroupId(groupId);
pojo.setUserId(request.getUserId());
pojo.setRole(request.getRole());
userRoleDao.insertAndReturnId(pojo);
}
public void changeMemberRole(Integer groupId, Integer userId, String role) {
if (!userRoleDao.hasRole(userId, groupId, role)) {
// TODO 最多 20 个组长
userRoleDao.deleteByUserIdAndGroupId(userId, groupId);
UserRolePojo pojo = new UserRolePojo();
pojo.setUserId(userId);
pojo.setGroupId(groupId);
pojo.setRole(role);
userRoleDao.insertAndReturnId(pojo);
}
}
}

13
core/src/main/java/com/databasir/core/domain/login/data/AccessTokenRefreshRequest.java

@ -0,0 +1,13 @@
package com.databasir.core.domain.login.data;
import lombok.Data;
import javax.validation.constraints.NotBlank;
@Data
public class AccessTokenRefreshRequest {
@NotBlank
private String refreshToken;
}

15
core/src/main/java/com/databasir/core/domain/login/data/AccessTokenRefreshResponse.java

@ -0,0 +1,15 @@
package com.databasir.core.domain.login.data;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class AccessTokenRefreshResponse {
private String accessToken;
private Long accessTokenExpireAt;
}

31
core/src/main/java/com/databasir/core/domain/login/data/LoginKeyResponse.java

@ -0,0 +1,31 @@
package com.databasir.core.domain.login.data;
import lombok.*;
import java.time.LocalDateTime;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@EqualsAndHashCode
public class LoginKeyResponse {
private Integer userId;
private String accessToken;
private LocalDateTime accessTokenExpireAt;
private String refreshToken;
private LocalDateTime refreshTokenExpireAt;
public boolean accessTokenIsExpired() {
return accessTokenExpireAt.isBefore(LocalDateTime.now());
}
public boolean refreshTokenIsExpired() {
return refreshTokenExpireAt.isBefore(LocalDateTime.now());
}
}

88
core/src/main/java/com/databasir/core/domain/login/service/LoginService.java

@ -0,0 +1,88 @@
package com.databasir.core.domain.login.service;
import com.databasir.core.domain.DomainErrors;
import com.databasir.core.domain.login.data.AccessTokenRefreshRequest;
import com.databasir.core.domain.login.data.AccessTokenRefreshResponse;
import com.databasir.core.domain.login.data.LoginKeyResponse;
import com.databasir.core.infrastructure.jwt.JwtTokens;
import com.databasir.dao.impl.LoginDao;
import com.databasir.dao.impl.UserDao;
import com.databasir.dao.tables.pojos.LoginPojo;
import com.databasir.dao.tables.pojos.UserPojo;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.Optional;
import java.util.UUID;
@Service
@RequiredArgsConstructor
@Slf4j
public class LoginService {
private final LoginDao loginDao;
private final UserDao userDao;
private final JwtTokens jwtTokens;
public AccessTokenRefreshResponse refreshAccessTokens(AccessTokenRefreshRequest request) {
LoginPojo login = loginDao.selectByRefreshToken(request.getRefreshToken())
.orElseThrow(DomainErrors.ACCESS_TOKEN_REFRESH_INVALID::exception);
// refresh-token 已过期
if (login.getRefreshTokenExpireAt().isBefore(LocalDateTime.now())) {
throw DomainErrors.REFRESH_TOKEN_EXPIRED.exception();
}
// access-token 未过期就开始刷新有可能是 refresh-token 泄露了,删除 refresh-token
if (login.getAccessTokenExpireAt().isAfter(LocalDateTime.now())) {
log.warn("invalid access token refresh operation: request = {}, login = {}", request, login);
loginDao.deleteByUserId(login.getUserId());
throw DomainErrors.ACCESS_TOKEN_REFRESH_INVALID.exception();
}
UserPojo user = userDao.selectById(login.getUserId());
String accessToken = jwtTokens.accessToken(user.getEmail());
LocalDateTime accessTokenExpireAt = jwtTokens.expireAt(accessToken);
loginDao.updateAccessToken(accessToken, accessTokenExpireAt, user.getId());
Instant instant = accessTokenExpireAt.atZone(ZoneId.systemDefault()).toInstant();
long accessTokenExpireAtMilli = instant.toEpochMilli();
return new AccessTokenRefreshResponse(accessToken, accessTokenExpireAtMilli);
}
public Optional<LoginKeyResponse> getLoginKey(Integer userId) {
return loginDao.selectByUserId(userId)
.map(loginPojo -> LoginKeyResponse.builder()
.accessToken(loginPojo.getAccessToken())
.accessTokenExpireAt(loginPojo.getAccessTokenExpireAt())
.refreshToken(loginPojo.getRefreshToken())
.refreshTokenExpireAt(loginPojo.getRefreshTokenExpireAt())
.build());
}
public LoginKeyResponse generate(Integer userId) {
UserPojo user = userDao.selectById(userId);
String accessToken = jwtTokens.accessToken(user.getEmail());
LocalDateTime accessTokenExpireAt = jwtTokens.expireAt(accessToken);
String refreshToken = UUID.randomUUID().toString().replace("-", "");
LocalDateTime refreshTokenExpireAt = LocalDateTime.now().plusDays(15);
LoginPojo loginPojo = new LoginPojo();
loginPojo.setAccessToken(accessToken);
loginPojo.setAccessTokenExpireAt(accessTokenExpireAt);
loginPojo.setRefreshToken(refreshToken);
loginPojo.setRefreshTokenExpireAt(refreshTokenExpireAt);
loginPojo.setUserId(userId);
loginDao.insertOnDuplicateKeyUpdate(loginPojo);
return LoginKeyResponse.builder()
.userId(userId)
.accessToken(accessToken)
.accessTokenExpireAt(accessTokenExpireAt)
.refreshToken(refreshToken)
.refreshTokenExpireAt(refreshTokenExpireAt)
.build();
}
}

41
core/src/main/java/com/databasir/core/domain/project/converter/DataSourcePojoConverter.java

@ -0,0 +1,41 @@
package com.databasir.core.domain.project.converter;
import com.databasir.core.domain.project.data.DataSourcePropertyValue;
import com.databasir.core.domain.project.data.ProjectCreateRequest;
import com.databasir.core.domain.project.data.ProjectUpdateRequest;
import com.databasir.dao.tables.pojos.DataSourcePojo;
import com.databasir.dao.tables.pojos.DataSourcePropertyPojo;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import java.util.List;
import java.util.stream.Collectors;
@Mapper(componentModel = "spring")
public interface DataSourcePojoConverter {
@Mapping(target = "id", ignore = true)
@Mapping(target = "createAt", ignore = true)
@Mapping(target = "updateAt", ignore = true)
@Mapping(target = "password", source = "password")
DataSourcePojo of(ProjectCreateRequest.DataSourceCreateRequest request,
String password,
Integer projectId);
@Mapping(target = "updateAt", ignore = true)
@Mapping(target = "createAt", ignore = true)
@Mapping(target = "password", source = "password")
DataSourcePojo of(ProjectUpdateRequest.DataSourceUpdateRequest request,
String password,
Integer projectId);
@Mapping(target = "id", ignore = true)
@Mapping(target = "createAt", ignore = true)
DataSourcePropertyPojo of(DataSourcePropertyValue propertyValues, Integer dataSourceId);
default List<DataSourcePropertyPojo> of(List<DataSourcePropertyValue> propertyValues,
Integer dataSourceId) {
return propertyValues.stream().map(value -> of(value, dataSourceId)).collect(Collectors.toList());
}
}

34
core/src/main/java/com/databasir/core/domain/project/converter/ProjectPojoConverter.java

@ -0,0 +1,34 @@
package com.databasir.core.domain.project.converter;
import com.databasir.core.domain.project.data.ProjectCreateRequest;
import com.databasir.core.domain.project.data.ProjectDetailResponse;
import com.databasir.core.domain.project.data.ProjectSimpleResponse;
import com.databasir.core.domain.project.data.ProjectUpdateRequest;
import com.databasir.core.infrastructure.converter.JsonConverter;
import com.databasir.dao.tables.pojos.DataSourcePojo;
import com.databasir.dao.tables.pojos.DataSourcePropertyPojo;
import com.databasir.dao.tables.pojos.ProjectPojo;
import com.databasir.dao.tables.pojos.ProjectSyncRulePojo;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.ReportingPolicy;
import java.util.List;
@Mapper(componentModel = "spring", unmappedTargetPolicy = ReportingPolicy.IGNORE, uses = JsonConverter.class)
public interface ProjectPojoConverter {
ProjectPojo of(ProjectCreateRequest request);
ProjectPojo of(ProjectUpdateRequest request);
@Mapping(target = "ignoreTableNameRegexArray", source = "request.ignoreTableNameRegexes")
@Mapping(target = "ignoreColumnNameRegexArray", source = "request.ignoreColumnNameRegexes")
ProjectSyncRulePojo of(ProjectCreateRequest.ProjectSyncRuleCreateRequest request,
Integer projectId);
@Mapping(target = "ignoreTableNameRegexArray", source = "request.ignoreTableNameRegexes")
@Mapping(target = "ignoreColumnNameRegexArray", source = "request.ignoreColumnNameRegexes")
ProjectSyncRulePojo of(ProjectUpdateRequest.ProjectSyncRuleUpdateRequest request,
Integer projectId);
}

35
core/src/main/java/com/databasir/core/domain/project/converter/ProjectResponseConverter.java

@ -0,0 +1,35 @@
package com.databasir.core.domain.project.converter;
import com.databasir.core.domain.project.data.ProjectDetailResponse;
import com.databasir.core.domain.project.data.ProjectSimpleResponse;
import com.databasir.core.infrastructure.converter.JsonConverter;
import com.databasir.dao.tables.pojos.DataSourcePojo;
import com.databasir.dao.tables.pojos.DataSourcePropertyPojo;
import com.databasir.dao.tables.pojos.ProjectPojo;
import com.databasir.dao.tables.pojos.ProjectSyncRulePojo;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.ReportingPolicy;
import java.util.List;
@Mapper(componentModel = "spring", unmappedTargetPolicy = ReportingPolicy.IGNORE, uses = JsonConverter.class)
public interface ProjectResponseConverter {
@Mapping(target = "id", source = "database.id")
@Mapping(target = "createAt", source = "database.createAt")
ProjectDetailResponse toResponse(ProjectPojo database,
ProjectDetailResponse.DataSourceResponse dataSource,
ProjectDetailResponse.ProjectSyncRuleResponse projectSyncRule);
ProjectDetailResponse.DataSourceResponse toResponse(DataSourcePojo dataSource,
List<DataSourcePropertyPojo> properties);
@Mapping(target = "ignoreTableNameRegexes", source = "ignoreTableNameRegexArray")
@Mapping(target = "ignoreColumnNameRegexes", source = "ignoreColumnNameRegexArray")
ProjectDetailResponse.ProjectSyncRuleResponse toResponse(ProjectSyncRulePojo rule);
@Mapping(target = "id", source = "project.id")
@Mapping(target = "createAt", source = "project.createAt")
ProjectSimpleResponse toSimple(ProjectPojo project, DataSourcePojo dataSource);
}

16
core/src/main/java/com/databasir/core/domain/project/data/DataSourcePropertyValue.java

@ -0,0 +1,16 @@
package com.databasir.core.domain.project.data;
import lombok.Data;
import javax.validation.constraints.NotBlank;
@Data
public class DataSourcePropertyValue {
@NotBlank
private String key;
@NotBlank
private String value;
}

59
core/src/main/java/com/databasir/core/domain/project/data/ProjectCreateRequest.java

@ -0,0 +1,59 @@
package com.databasir.core.domain.project.data;
import lombok.Data;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import java.util.ArrayList;
import java.util.List;
@Data
public class ProjectCreateRequest {
@NotBlank
private String name;
@NotBlank
private String description;
@NotNull
private Integer groupId;
@NotNull
private ProjectCreateRequest.DataSourceCreateRequest dataSource;
@NotNull
private ProjectCreateRequest.ProjectSyncRuleCreateRequest projectSyncRule;
@Data
public static class DataSourceCreateRequest {
@NotBlank
private String username;
@NotBlank
private String password;
@NotBlank
private String url;
@NotBlank
private String databaseName;
@NotBlank
private String databaseType;
private List<DataSourcePropertyValue> properties = new ArrayList<>();
}
@Data
public static class ProjectSyncRuleCreateRequest {
private List<String> ignoreTableNameRegexes = new ArrayList<>();
private List<String> ignoreColumnNameRegexes = new ArrayList<>();
}
}

56
core/src/main/java/com/databasir/core/domain/project/data/ProjectDetailResponse.java

@ -0,0 +1,56 @@
package com.databasir.core.domain.project.data;
import lombok.Data;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
@Data
public class ProjectDetailResponse {
private Integer id;
private String name;
private String description;
private DataSourceResponse dataSource;
private ProjectSyncRuleResponse projectSyncRule;
private LocalDateTime createAt;
@Data
public static class DataSourceResponse {
private Integer id;
private String username;
private String url;
private String databaseName;
private String databaseType;
private List<DataSourcePropertyValue> properties = new ArrayList<>();
private LocalDateTime updateAt;
private LocalDateTime createAt;
}
@Data
public static class ProjectSyncRuleResponse {
private Integer id;
private List<String> ignoreTableNameRegexes = new ArrayList<>();
private List<String> ignoreColumnNameRegexes = new ArrayList<>();
private LocalDateTime createAt;
}
}

43
core/src/main/java/com/databasir/core/domain/project/data/ProjectListCondition.java

@ -0,0 +1,43 @@
package com.databasir.core.domain.project.data;
import com.databasir.dao.Tables;
import lombok.Data;
import org.jooq.Condition;
import org.jooq.impl.DSL;
import java.util.ArrayList;
import java.util.List;
@Data
public class ProjectListCondition {
private String nameContains;
private String databaseNameContains;
private String databaseType;
private Integer groupId;
public Condition toCondition() {
List<Condition> conditions = new ArrayList<>();
if (nameContains != null) {
Condition condition = Tables.PROJECT.NAME.contains(nameContains);
conditions.add(condition);
}
if (databaseNameContains != null) {
Condition condition = Tables.DATA_SOURCE.DATABASE_NAME.contains(databaseNameContains);
conditions.add(condition);
}
if (databaseType != null) {
Condition condition = Tables.DATA_SOURCE.DATABASE_TYPE.eq(databaseType);
conditions.add(condition);
}
if (groupId != null) {
Condition condition = Tables.PROJECT.GROUP_ID.eq(groupId);
conditions.add(condition);
}
conditions.add(Tables.PROJECT.DELETED.eq(false));
return conditions.stream().reduce(Condition::and).orElse(DSL.trueCondition());
}
}

22
core/src/main/java/com/databasir/core/domain/project/data/ProjectSimpleResponse.java

@ -0,0 +1,22 @@
package com.databasir.core.domain.project.data;
import lombok.Data;
import java.time.LocalDateTime;
@Data
public class ProjectSimpleResponse {
private Integer id;
private String name;
private String description;
private String databaseName;
private String databaseType;
private LocalDateTime createAt;
}

56
core/src/main/java/com/databasir/core/domain/project/data/ProjectUpdateRequest.java

@ -0,0 +1,56 @@
package com.databasir.core.domain.project.data;
import lombok.Data;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import java.util.ArrayList;
import java.util.List;
@Data
public class ProjectUpdateRequest {
@NotNull
private Integer id;
@NotBlank
private String name;
@NotBlank
private String description;
@NotNull
private ProjectUpdateRequest.DataSourceUpdateRequest dataSource;
@NotNull
private ProjectUpdateRequest.ProjectSyncRuleUpdateRequest projectSyncRule;
@Data
public static class DataSourceUpdateRequest {
@NotBlank
private String username;
private String password;
@NotBlank
private String url;
@NotBlank
private String databaseName;
@NotBlank
private String databaseType;
private List<DataSourcePropertyValue> properties = new ArrayList<>();
}
@Data
public static class ProjectSyncRuleUpdateRequest {
private List<String> ignoreTableNameRegexes = new ArrayList<>();
private List<String> ignoreColumnNameRegexes = new ArrayList<>();
}
}

141
core/src/main/java/com/databasir/core/domain/project/service/ProjectService.java

@ -0,0 +1,141 @@
package com.databasir.core.domain.project.service;
import com.databasir.common.codec.Aes;
import com.databasir.core.domain.DomainErrors;
import com.databasir.core.domain.project.converter.DataSourcePojoConverter;
import com.databasir.core.domain.project.converter.ProjectPojoConverter;
import com.databasir.core.domain.project.converter.ProjectResponseConverter;
import com.databasir.core.domain.project.data.*;
import com.databasir.dao.impl.*;
import com.databasir.dao.tables.pojos.*;
import lombok.RequiredArgsConstructor;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Function;
import java.util.stream.Collectors;
@Service
@RequiredArgsConstructor
public class ProjectService {
private final ProjectDao projectDao;
private final ProjectSyncRuleDao projectSyncRuleDao;
private final DataSourceDao dataSourceDao;
private final SysKeyDao sysKeyDao;
private final DataSourcePropertyDao dataSourcePropertyDao;
private final DataSourcePojoConverter dataSourcePojoConverter;
private final ProjectPojoConverter projectPojoConverter;
private final ProjectResponseConverter projectResponseConverter;
public ProjectDetailResponse getOne(Integer id) {
return projectDao.selectOptionalById(id)
.map(schemaSource -> {
DataSourcePojo dataSource = dataSourceDao.selectByProjectId(id);
List<DataSourcePropertyPojo> properties = dataSourcePropertyDao.selectByDataSourceId(dataSource.getId());
ProjectDetailResponse.DataSourceResponse dataSourceResponse = projectResponseConverter.toResponse(dataSource, properties);
ProjectSyncRulePojo rule = projectSyncRuleDao.selectByProjectId(id);
ProjectDetailResponse.ProjectSyncRuleResponse ruleResponse = projectResponseConverter.toResponse(rule);
return projectResponseConverter.toResponse(schemaSource, dataSourceResponse, ruleResponse);
})
.orElseThrow(DomainErrors.PROJECT_NOT_FOUND::exception);
}
@Transactional
public void create(ProjectCreateRequest request) {
ProjectPojo project = projectPojoConverter.of(request);
Integer projectId = null;
try {
projectId = projectDao.insertAndReturnId(project);
} catch (DuplicateKeyException e) {
throw DomainErrors.PROJECT_NOT_FOUND.exception();
}
String newPassword = encryptPassword(request.getDataSource().getPassword()).get();
DataSourcePojo dataSource = dataSourcePojoConverter.of(request.getDataSource(), newPassword, projectId);
Integer dataSourceId = dataSourceDao.insertAndReturnId(dataSource);
List<DataSourcePropertyValue> propertyValues = request.getDataSource().getProperties();
List<DataSourcePropertyPojo> properties = dataSourcePojoConverter.of(propertyValues, dataSourceId);
dataSourcePropertyDao.batchInsert(properties);
// TODO redesign it
ProjectSyncRulePojo syncRule = projectPojoConverter.of(request.getProjectSyncRule(), projectId);
projectSyncRuleDao.insertAndReturnId(syncRule);
}
@Transactional
public void update(Integer groupId, ProjectUpdateRequest request) {
Integer projectId = request.getId();
if (projectDao.exists(groupId, projectId)) {
// update dataSource
String newPassword = encryptPassword(request.getDataSource().getPassword()).orElse(null);
DataSourcePojo dataSource = dataSourcePojoConverter.of(request.getDataSource(), newPassword, projectId);
dataSourceDao.updateByProjectId(dataSource);
// update connection property
Integer dataSourceId = dataSourceDao.selectByProjectId(projectId).getId();
List<DataSourcePropertyValue> propertyValues = request.getDataSource().getProperties();
List<DataSourcePropertyPojo> properties = dataSourcePojoConverter.of(propertyValues, dataSourceId);
if (properties.isEmpty()) {
dataSourcePropertyDao.deleteByDataSourceId(dataSourceId);
} else {
dataSourcePropertyDao.deleteByDataSourceId(dataSourceId);
dataSourcePropertyDao.batchInsert(properties);
}
// update project sync rule TODO redesign it
ProjectSyncRulePojo syncRule = projectPojoConverter.of(request.getProjectSyncRule(), projectId);
projectSyncRuleDao.updateByProjectId(syncRule);
// update project info
ProjectPojo project = projectPojoConverter.of(request);
projectDao.updateById(project);
} else {
throw DomainErrors.PROJECT_NOT_FOUND.exception();
}
}
private Optional<String> encryptPassword(String password) {
if (!StringUtils.hasText(password)) {
return Optional.empty();
}
SysKeyPojo sysKey = sysKeyDao.selectTopOne();
// String decryptedPassword = Rsa.decryptFromBase64DataByPrivateKey(password, sysKey.getRsaPrivateKey());
return Optional.of(Aes.encryptToBase64Data(password, sysKey.getAesKey()));
}
public void delete(Integer projectId) {
projectDao.updateDeletedById(true, projectId);
}
public Page<ProjectSimpleResponse> list(Pageable page, ProjectListCondition condition) {
Page<ProjectPojo> pageData = projectDao.selectByCondition(page, condition.toCondition());
List<Integer> projectIds = pageData.getContent()
.stream()
.map(ProjectPojo::getId)
.collect(Collectors.toList());
Map<Integer, DataSourcePojo> dataSourceMapByProjectId = dataSourceDao.selectInProjectIds(projectIds)
.stream()
.collect(Collectors.toMap(DataSourcePojo::getProjectId, Function.identity()));
return pageData.map(project -> {
DataSourcePojo dataSource = dataSourceMapByProjectId.get(project.getId());
return projectResponseConverter.toSimple(project, dataSource);
});
}
}

17
core/src/main/java/com/databasir/core/domain/system/data/SystemEmailResponse.java

@ -0,0 +1,17 @@
package com.databasir.core.domain.system.data;
import lombok.Data;
import java.time.LocalDateTime;
@Data
public class SystemEmailResponse {
private String username;
private String smtpHost;
private Integer smtpPort;
private LocalDateTime createAt;
}

24
core/src/main/java/com/databasir/core/domain/system/data/SystemEmailUpdateRequest.java

@ -0,0 +1,24 @@
package com.databasir.core.domain.system.data;
import lombok.Data;
import javax.validation.constraints.*;
@Data
public class SystemEmailUpdateRequest {
@NotBlank
@Email
private String username;
private String password;
@NotBlank
private String smtpHost;
@NotNull
@Min(0L)
@Max(65535L)
private Integer smtpPort;
}

106
core/src/main/java/com/databasir/core/domain/system/service/SystemService.java

@ -0,0 +1,106 @@
package com.databasir.core.domain.system.service;
import com.databasir.common.codec.Aes;
import com.databasir.common.codec.Rsa;
import com.databasir.core.domain.DomainErrors;
import com.databasir.core.domain.system.data.SystemEmailResponse;
import com.databasir.core.domain.system.data.SystemEmailUpdateRequest;
import com.databasir.dao.impl.SysKeyDao;
import com.databasir.dao.impl.SysMailDao;
import com.databasir.dao.impl.UserDao;
import com.databasir.dao.impl.UserRoleDao;
import com.databasir.dao.tables.pojos.SysKeyPojo;
import com.databasir.dao.tables.pojos.SysMailPojo;
import com.databasir.dao.tables.pojos.UserPojo;
import com.databasir.dao.tables.pojos.UserRolePojo;
import lombok.RequiredArgsConstructor;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import javax.annotation.PostConstruct;
import java.util.Optional;
@Service
@RequiredArgsConstructor
public class SystemService {
private final SysKeyDao sysKeyDao;
private final SysMailDao sysMailDao;
private final UserDao userDao;
private final UserRoleDao userRoleDao;
private BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
@PostConstruct
public void postInit() {
sysKeyDao.selectOptionTopOne()
.orElseGet(() -> {
SysKeyPojo pojo = new SysKeyPojo();
pojo.setAesKey(Aes.randomBase64Key());
Rsa.RsaBase64Key key = Rsa.generateBase64Key();
pojo.setRsaPublicKey(key.getPublicBase64Key());
pojo.setRsaPrivateKey(key.getPrivateBase64Key());
sysKeyDao.insertAndReturnId(pojo);
return pojo;
});
String email = "admin@databasir.com";
String username = "databasir";
Optional<UserPojo> userOpt = userDao.selectByEmail(email);
if (!userOpt.isPresent()) {
UserPojo admin = new UserPojo();
admin.setEmail(email);
admin.setUsername(username);
admin.setPassword(bCryptPasswordEncoder.encode(username));
admin.setEnabled(true);
admin.setNickname("Databasir Admin");
Integer userId = userDao.insertAndReturnId(admin);
UserRolePojo role = new UserRolePojo();
role.setUserId(userId);
role.setRole("SYS_OWNER");
userRoleDao.insertAndReturnId(role);
}
}
public void renewKey() {
// TODO
}
public Optional<SystemEmailResponse> getEmailSetting() {
return sysMailDao.selectOptionTopOne()
.map(mail -> {
SystemEmailResponse response = new SystemEmailResponse();
response.setSmtpHost(mail.getSmtpHost());
response.setSmtpPort(mail.getSmtpPort());
response.setUsername(mail.getUsername());
response.setCreateAt(mail.getCreateAt());
return response;
});
}
public void updateEmailSetting(SystemEmailUpdateRequest request) {
Optional<Integer> idOpt = sysMailDao.selectOptionTopOne().map(SysMailPojo::getId);
SysMailPojo sysMailPojo = new SysMailPojo();
sysMailPojo.setSmtpHost(request.getSmtpHost());
sysMailPojo.setSmtpPort(request.getSmtpPort());
sysMailPojo.setUsername(request.getUsername());
idOpt.ifPresent(sysMailPojo::setId);
if (request.getPassword() != null) {
// TODO encrypt password ?
sysMailPojo.setPassword(request.getPassword());
}
if (idOpt.isPresent()) {
if (!StringUtils.hasText(request.getPassword())) {
throw DomainErrors.CONNECT_DATABASE_FAILED.exception();
}
sysMailDao.updateById(sysMailPojo);
} else {
sysMailDao.insertAndReturnId(sysMailPojo);
}
}
}

13
core/src/main/java/com/databasir/core/domain/user/converter/UserPojoConverter.java

@ -0,0 +1,13 @@
package com.databasir.core.domain.user.converter;
import com.databasir.core.domain.user.data.UserCreateRequest;
import com.databasir.dao.tables.pojos.UserPojo;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
@Mapper(componentModel = "spring")
public interface UserPojoConverter {
@Mapping(target = "password", source = "hashedPassword")
UserPojo of(UserCreateRequest request, String hashedPassword);
}

32
core/src/main/java/com/databasir/core/domain/user/converter/UserResponseConverter.java

@ -0,0 +1,32 @@
package com.databasir.core.domain.user.converter;
import com.databasir.core.domain.user.data.UserDetailResponse;
import com.databasir.core.domain.user.data.UserPageResponse;
import com.databasir.dao.tables.pojos.UserPojo;
import com.databasir.dao.tables.pojos.UserRolePojo;
import org.mapstruct.Mapper;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@Mapper(componentModel = "spring")
public interface UserResponseConverter {
default UserDetailResponse detailResponse(UserPojo user,
List<UserRolePojo> userRoles,
Map<Integer, String> groupNameMapById) {
List<UserDetailResponse.UserRoleDetailResponse> roles = userRoles.stream()
.map(pojo -> userRoleDetailResponse(pojo, groupNameMapById.get(pojo.getGroupId())))
.collect(Collectors.toList());
return detailResponse(user, roles);
}
UserDetailResponse detailResponse(UserPojo pojo, List<UserDetailResponse.UserRoleDetailResponse> roles);
UserDetailResponse.UserRoleDetailResponse userRoleDetailResponse(UserRolePojo pojo, String groupName);
UserPageResponse pageResponse(UserPojo pojo, Boolean isSysOwner, List<Integer> inGroupIds);
}

28
core/src/main/java/com/databasir/core/domain/user/data/UserCreateRequest.java

@ -0,0 +1,28 @@
package com.databasir.core.domain.user.data;
import lombok.Data;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
@Data
public class UserCreateRequest {
private String avatar;
@NotBlank
private String username;
@NotBlank
private String nickname;
@NotBlank
private String email;
@NotBlank
private String password;
@NotNull
private Boolean enabled;
}

39
core/src/main/java/com/databasir/core/domain/user/data/UserDetailResponse.java

@ -0,0 +1,39 @@
package com.databasir.core.domain.user.data;
import lombok.Data;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
@Data
public class UserDetailResponse {
private Integer id;
private String username;
private String nickname;
private String avatar;
private String email;
private Boolean enabled;
private List<UserRoleDetailResponse> roles = new ArrayList<>();
private LocalDateTime createAt;
@Data
public static class UserRoleDetailResponse {
private String role;
private Integer groupId;
private String groupName;
private LocalDateTime createAt;
}
}

16
core/src/main/java/com/databasir/core/domain/user/data/UserLoginRequest.java

@ -0,0 +1,16 @@
package com.databasir.core.domain.user.data;
import lombok.Data;
import javax.validation.constraints.NotBlank;
@Data
public class UserLoginRequest {
@NotBlank
private String username;
@NotBlank
private String password;
}

32
core/src/main/java/com/databasir/core/domain/user/data/UserLoginResponse.java

@ -0,0 +1,32 @@
package com.databasir.core.domain.user.data;
import lombok.Data;
import java.util.List;
@Data
public class UserLoginResponse {
private Integer id;
private String nickname;
private String email;
private String username;
private String accessToken;
private long accessTokenExpireAt;
private String refreshToken;
private List<RoleResponse> roles;
@Data
public static class RoleResponse {
private String role;
private Integer groupId;
}
}

15
core/src/main/java/com/databasir/core/domain/user/data/UserNicknameUpdateRequest.java

@ -0,0 +1,15 @@
package com.databasir.core.domain.user.data;
import lombok.Data;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Size;
@Data
public class UserNicknameUpdateRequest {
@NotBlank
@Size(max = 32, min = 1)
private String nickname;
}

51
core/src/main/java/com/databasir/core/domain/user/data/UserPageCondition.java

@ -0,0 +1,51 @@
package com.databasir.core.domain.user.data;
import lombok.Data;
import org.jooq.Condition;
import org.jooq.impl.DSL;
import java.util.ArrayList;
import java.util.List;
import static com.databasir.dao.Tables.USER;
@Data
public class UserPageCondition {
private String nicknameContains;
private String usernameContains;
private String emailContains;
private String nicknameOrUsernameOrEmailContains;
private Boolean enabled;
public Condition toCondition() {
List<Condition> conditions = new ArrayList<>();
if (nicknameContains != null) {
Condition condition = USER.NICKNAME.contains(nicknameContains);
conditions.add(condition);
}
if (usernameContains != null) {
Condition condition = USER.USERNAME.contains(usernameContains);
conditions.add(condition);
}
if (emailContains != null) {
Condition condition = USER.EMAIL.contains(emailContains);
conditions.add(condition);
}
if (enabled != null) {
Condition condition = USER.ENABLED.eq(enabled);
conditions.add(condition);
}
if (nicknameOrUsernameOrEmailContains != null) {
Condition condition = USER.EMAIL.contains(nicknameOrUsernameOrEmailContains)
.or(USER.USERNAME.contains(nicknameOrUsernameOrEmailContains))
.or(USER.EMAIL.contains(nicknameOrUsernameOrEmailContains));
conditions.add(condition);
}
return conditions.stream().reduce(Condition::and).orElse(DSL.trueCondition());
}
}

27
core/src/main/java/com/databasir/core/domain/user/data/UserPageResponse.java

@ -0,0 +1,27 @@
package com.databasir.core.domain.user.data;
import lombok.Data;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
@Data
public class UserPageResponse {
private Integer id;
private String username;
private String nickname;
private String email;
private Boolean enabled;
private Boolean isSysOwner;
private List<Integer> inGroupIds = new ArrayList<>();
private LocalDateTime createAt;
}

22
core/src/main/java/com/databasir/core/domain/user/data/UserPasswordUpdateRequest.java

@ -0,0 +1,22 @@
package com.databasir.core.domain.user.data;
import lombok.Data;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Size;
@Data
public class UserPasswordUpdateRequest {
@NotBlank
private String originPassword;
@NotBlank
@Size(min = 6, max = 32)
private String newPassword;
@NotBlank
@Size(min = 6, max = 32)
private String confirmNewPassword;
}

13
core/src/main/java/com/databasir/core/domain/user/data/UserRoleAssignRequest.java

@ -0,0 +1,13 @@
package com.databasir.core.domain.user.data;
import lombok.Data;
@Data
public class UserRoleAssignRequest {
private Integer userId;
private String role;
private Integer groupId;
}

146
core/src/main/java/com/databasir/core/domain/user/service/UserService.java

@ -0,0 +1,146 @@
package com.databasir.core.domain.user.service;
import com.databasir.core.domain.DomainErrors;
import com.databasir.core.domain.user.converter.UserPojoConverter;
import com.databasir.core.domain.user.converter.UserResponseConverter;
import com.databasir.core.domain.user.data.*;
import com.databasir.core.infrastructure.mail.MailSender;
import com.databasir.dao.impl.GroupDao;
import com.databasir.dao.impl.SysMailDao;
import com.databasir.dao.impl.UserDao;
import com.databasir.dao.impl.UserRoleDao;
import com.databasir.dao.tables.pojos.GroupPojo;
import com.databasir.dao.tables.pojos.UserPojo;
import com.databasir.dao.tables.pojos.UserRolePojo;
import lombok.RequiredArgsConstructor;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.*;
import static java.util.stream.Collectors.*;
@Service
@RequiredArgsConstructor
public class UserService {
private final UserDao userDao;
private final UserRoleDao userRoleDao;
private final GroupDao groupDao;
private final SysMailDao sysMailDao;
private final UserPojoConverter userPojoConverter;
private final UserResponseConverter userResponseConverter;
private final MailSender mailSender;
private BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
public Page<UserPageResponse> list(Pageable pageable, UserPageCondition condition) {
Page<UserPojo> users = userDao.selectByPage(pageable, condition.toCondition());
List<Integer> userIds = users.getContent()
.stream()
.map(UserPojo::getId)
.collect(toList());
List<UserRolePojo> userRoles = userRoleDao.selectByUserIds(userIds);
Map<Integer, List<Integer>> groupIdMapByUserId = userRoles
.stream()
.filter(ur -> ur.getGroupId() != null)
.collect(groupingBy(UserRolePojo::getUserId, mapping(UserRolePojo::getGroupId, toList())));
Map<Integer, List<UserRolePojo>> sysOwnerGroupByUserId = userRoles.stream()
.filter(ur -> ur.getRole().equals("SYS_OWNER"))
.collect(groupingBy(UserRolePojo::getUserId));
return users.map(user ->
userResponseConverter.pageResponse(user, sysOwnerGroupByUserId.containsKey(user.getId()),
groupIdMapByUserId.get(user.getId())));
}
@Transactional
public void create(UserCreateRequest userCreateRequest) {
userDao.selectByEmailOrUsername(userCreateRequest.getUsername()).ifPresent(data -> {
throw DomainErrors.USERNAME_OR_EMAIL_DUPLICATE.exception();
});
String hashedPassword = bCryptPasswordEncoder.encode(userCreateRequest.getPassword());
UserPojo pojo = userPojoConverter.of(userCreateRequest, hashedPassword);
try {
userDao.insertAndReturnId(pojo);
} catch (DuplicateKeyException e) {
throw DomainErrors.USERNAME_OR_EMAIL_DUPLICATE.exception();
}
}
public UserDetailResponse get(Integer userId) {
UserPojo pojo = userDao.selectById(userId);
List<UserRolePojo> roles = userRoleDao.selectByUserIds(Collections.singletonList(userId));
List<Integer> groupIds = roles.stream()
.map(UserRolePojo::getGroupId)
.filter(Objects::nonNull)
.collect(toList());
Map<Integer, String> groupNameMapById = groupDao.selectInIds(groupIds)
.stream()
.collect(toMap(GroupPojo::getId, GroupPojo::getName));
return userResponseConverter.detailResponse(pojo, roles, groupNameMapById);
}
@Transactional
public String renewPassword(Integer userId) {
UserPojo userPojo = userDao.selectById(userId);
String randomPassword = UUID.randomUUID().toString()
.replace("-", "")
.substring(0, 8);
String hashedPassword = bCryptPasswordEncoder.encode(randomPassword);
userDao.updatePassword(userId, hashedPassword);
sysMailDao.selectOptionTopOne()
.ifPresent(mailPojo -> {
String subject = "Databasir 密码重置提醒";
String message = "您的密码已被重置,新密码为:" + randomPassword;
mailSender.send(mailPojo, userPojo.getEmail(), subject, message);
});
return randomPassword;
}
public void switchEnableStatus(Integer userId, Boolean enable) {
userDao.updateEnabledByUserId(userId, enable);
}
public void removeSysOwnerFrom(Integer userId) {
if (userRoleDao.hasRole(userId, "SYS_OWNER")) {
userRoleDao.deleteRole(userId, "SYS_OWNER");
}
}
public void addSysOwnerTo(Integer userId) {
if (!userRoleDao.hasRole(userId, "SYS_OWNER")) {
UserRolePojo role = new UserRolePojo();
role.setUserId(userId);
role.setRole("SYS_OWNER");
userRoleDao.insertAndReturnId(role);
}
}
public void updatePassword(Integer userId, UserPasswordUpdateRequest request) {
if (!Objects.equals(request.getNewPassword(), request.getConfirmNewPassword())) {
throw DomainErrors.UPDATE_PASSWORD_CONFIRM_FAILED.exception();
}
UserPojo userPojo = userDao.selectById(userId);
if (!bCryptPasswordEncoder.matches(request.getOriginPassword(), userPojo.getPassword())) {
throw DomainErrors.ORIGIN_PASSWORD_NOT_CORRECT.exception();
}
String newHashedPassword = bCryptPasswordEncoder.encode(request.getNewPassword());
userDao.updatePassword(userId, newHashedPassword);
}
public void updateNickname(Integer userId, UserNicknameUpdateRequest request) {
UserPojo userPojo = userDao.selectById(userId);
userPojo.setNickname(request.getNickname());
userDao.updateById(userPojo);
}
}

15
core/src/main/java/com/databasir/core/infrastructure/connection/DatabaseConnectionFactory.java

@ -0,0 +1,15 @@
package com.databasir.core.infrastructure.connection;
import java.sql.Connection;
import java.util.Properties;
public interface DatabaseConnectionFactory {
boolean support(String databaseType);
Connection getConnection(String username,
String password,
String url,
String schema,
Properties properties);
}

39
core/src/main/java/com/databasir/core/infrastructure/connection/DatabaseConnectionService.java

@ -0,0 +1,39 @@
package com.databasir.core.infrastructure.connection;
import com.databasir.common.codec.Aes;
import com.databasir.core.domain.DomainErrors;
import com.databasir.dao.impl.SysKeyDao;
import com.databasir.dao.tables.pojos.DataSourcePojo;
import com.databasir.dao.tables.pojos.DataSourcePropertyPojo;
import com.databasir.dao.tables.pojos.SysKeyPojo;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import java.sql.Connection;
import java.util.List;
import java.util.Properties;
@Component
@RequiredArgsConstructor
public class DatabaseConnectionService {
private final List<DatabaseConnectionFactory> factories;
private final SysKeyDao sysKeyDao;
public Connection create(DataSourcePojo dataSource,
List<DataSourcePropertyPojo> dataSourceProperties) {
SysKeyPojo sysKey = sysKeyDao.selectTopOne();
String username = dataSource.getUsername();
String password = Aes.decryptFromBase64Data(dataSource.getPassword(), sysKey.getAesKey());
String url = dataSource.getUrl();
Properties info = new Properties();
dataSourceProperties.forEach(prop -> info.put(prop.getKey(), prop.getValue()));
return factories.stream()
.filter(factory -> factory.support(dataSource.getDatabaseType()))
.findFirst()
.orElseThrow(DomainErrors.NOT_SUPPORT_DATABASE_TYPE::exception)
.getConnection(username, password, url, dataSource.getDatabaseName(), info);
}
}

10
core/src/main/java/com/databasir/core/infrastructure/connection/DatabaseTypes.java

@ -0,0 +1,10 @@
package com.databasir.core.infrastructure.connection;
public interface DatabaseTypes {
String MYSQL = "mysql";
String POSTGRESQL = "postgresql";
String ORACLE = "oracle";
}

39
core/src/main/java/com/databasir/core/infrastructure/connection/MysqlDatabaseConnectionFactory.java

@ -0,0 +1,39 @@
package com.databasir.core.infrastructure.connection;
import com.databasir.core.domain.DomainErrors;
import org.springframework.stereotype.Component;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Properties;
@Component
public class MysqlDatabaseConnectionFactory implements DatabaseConnectionFactory {
@Override
public boolean support(String databaseType) {
return DatabaseTypes.MYSQL.equalsIgnoreCase(databaseType);
}
@Override
public Connection getConnection(String username, String password, String url, String schema, Properties properties) {
try {
Class.forName("com.mysql.cj.jdbc.Driver");
} catch (ClassNotFoundException e) {
throw new IllegalStateException(e);
}
Properties info = new Properties();
info.put("user", username);
info.put("password", password);
info.putAll(properties);
String jdbcUrl = "jdbc:mysql://" + url + "/" + schema;
try {
return DriverManager.getConnection(jdbcUrl, info);
} catch (SQLException e) {
throw DomainErrors.CONNECT_DATABASE_FAILED.exception(e);
}
}
}

39
core/src/main/java/com/databasir/core/infrastructure/connection/PostgresqlDatabaseConnectionFactory.java

@ -0,0 +1,39 @@
package com.databasir.core.infrastructure.connection;
import com.databasir.core.domain.DomainErrors;
import org.springframework.stereotype.Component;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Properties;
@Component
public class PostgresqlDatabaseConnectionFactory implements DatabaseConnectionFactory {
@Override
public boolean support(String databaseType) {
return DatabaseTypes.POSTGRESQL.equalsIgnoreCase(databaseType);
}
@Override
public Connection getConnection(String username, String password, String url, String schema, Properties properties) {
try {
Class.forName("org.postgresql.Driver");
} catch (ClassNotFoundException e) {
throw new IllegalStateException(e);
}
Properties info = new Properties();
info.put("user", username);
info.put("password", password);
info.putAll(properties);
String jdbcUrl = "jdbc:postgresql://" + url + "/" + schema;
try {
return DriverManager.getConnection(jdbcUrl, info);
} catch (SQLException e) {
throw DomainErrors.CONNECT_DATABASE_FAILED.exception(e);
}
}
}

65
core/src/main/java/com/databasir/core/infrastructure/converter/JsonConverter.java

@ -0,0 +1,65 @@
package com.databasir.core.infrastructure.converter;
import com.databasir.core.domain.document.data.DatabaseDocumentResponse;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.jooq.JSON;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.List;
@Component
public class JsonConverter {
@Autowired
private ObjectMapper objectMapper;
public JSON toJson(List<String> array) {
try {
String json = objectMapper.writeValueAsString(array);
return JSON.valueOf(json);
} catch (JsonProcessingException e) {
throw new IllegalArgumentException(e);
}
}
public List<String> fromJson(JSON json) {
String data = json.data();
if (data == null) {
return Collections.emptyList();
} else {
try {
return objectMapper.readValue(data.getBytes(StandardCharsets.UTF_8),
new TypeReference<List<String>>() {
});
} catch (IOException e) {
throw new IllegalArgumentException(e);
}
}
}
public JSON toJson(DatabaseDocumentResponse response) {
try {
String json = objectMapper.writeValueAsString(response);
return JSON.valueOf(json);
} catch (JsonProcessingException e) {
throw new IllegalArgumentException(e);
}
}
public DatabaseDocumentResponse of(JSON json) {
try {
if (json == null) {
return null;
}
return objectMapper.readValue(json.data().getBytes(StandardCharsets.UTF_8), DatabaseDocumentResponse.class);
} catch (IOException e) {
throw new IllegalArgumentException(e);
}
}
}

61
core/src/main/java/com/databasir/core/infrastructure/jwt/JwtTokens.java

@ -0,0 +1,61 @@
package com.databasir.core.infrastructure.jwt;
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.auth0.jwt.interfaces.JWTVerifier;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.Date;
@Component
@Slf4j
public class JwtTokens {
// 15 minutes
private static final long ACCESS_EXPIRE_TIME = 1000 * 60 * 15;
public static final String TOKEN_PREFIX = "Bearer ";
private static final String ISSUER = "Databasir";
private static final String SECRET = "Databasir2022";
public String accessToken(String username) {
Algorithm algorithm = Algorithm.HMAC256(SECRET);
return JWT.create()
.withExpiresAt(new Date(new Date().getTime() + ACCESS_EXPIRE_TIME))
.withIssuer(ISSUER)
.withClaim("username", username)
.sign(algorithm);
}
public boolean verify(String token) {
JWTVerifier verifier = JWT.require(Algorithm.HMAC256(SECRET))
.withIssuer(ISSUER)
.build();
try {
verifier.verify(token);
return true;
} catch (JWTVerificationException e) {
log.warn("verify jwt token failed " + e.getMessage());
return false;
}
}
public String getUsername(String token) {
return JWT.decode(token).getClaim("username").asString();
}
public LocalDateTime expireAt(String token) {
long time = JWT.decode(token).getExpiresAt().getTime();
return Instant.ofEpochMilli(time)
.atZone(ZoneId.systemDefault())
.toLocalDateTime();
}
}

36
core/src/main/java/com/databasir/core/infrastructure/mail/MailSender.java

@ -0,0 +1,36 @@
package com.databasir.core.infrastructure.mail;
import com.databasir.dao.tables.pojos.SysMailPojo;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.JavaMailSenderImpl;
import org.springframework.stereotype.Component;
import java.nio.charset.StandardCharsets;
@Component
public class MailSender {
public void send(SysMailPojo mail, String to, String subject, String content) {
JavaMailSender sender = initJavaMailSender(mail);
SimpleMailMessage message = new SimpleMailMessage();
message.setFrom(mail.getUsername());
message.setTo(to);
message.setSubject(subject);
message.setText(content);
sender.send(message);
}
private JavaMailSender initJavaMailSender(SysMailPojo properties) {
JavaMailSenderImpl sender = new JavaMailSenderImpl();
sender.setHost(properties.getSmtpHost());
if (properties.getSmtpPort() != null) {
sender.setPort(properties.getSmtpPort());
}
sender.setUsername(properties.getUsername());
sender.setPassword(properties.getPassword());
sender.setProtocol("smtp");
sender.setDefaultEncoding(StandardCharsets.UTF_8.name());
return sender;
}
}

5
core/src/main/java/com/databasir/core/infrastructure/meta/DatabaseMetaResolver.java

@ -0,0 +1,5 @@
package com.databasir.core.infrastructure.meta;
public class DatabaseMetaResolver {
}

77
dao/build.gradle

@ -0,0 +1,77 @@
plugins {
id 'nu.studer.jooq'
id 'io.spring.dependency-management'
id 'org.springframework.boot' apply false
}
dependencies {
implementation "org.jooq:jooq:${jooqVersion}"
implementation "org.jooq:jooq-codegen:${jooqVersion}"
implementation 'org.springframework.boot:spring-boot-starter-data-jdbc'
jooqGenerator "mysql:mysql-connector-java:${mysqlConnectorVersion}"
// include self to use strategy
jooqGenerator project(':dao')
}
sourceSets {
main {
java.srcDirs += 'generated-src/jooq/main/java'
}
}
jooq {
version = "${jooqVersion}"
edition = nu.studer.gradle.jooq.JooqEdition.OSS
configurations {
databasir {
generateSchemaSourceOnCompilation = true
generationTool {
jdbc {
driver = 'com.mysql.cj.jdbc.Driver'
url = 'jdbc:mysql://localhost:3306/databasir'
user = 'root'
password = '123456'
properties {
property {
key = 'useSSL'
value = 'false'
}
property {
key = 'allowPublicKeyRetrieval'
value = 'true'
}
}
}
generator {
name = 'org.jooq.codegen.DefaultGenerator'
database {
name = 'org.jooq.meta.mysql.MySQLDatabase'
inputSchema = 'databasir'
excludes = 'flyway.*'
forcedTypes {
forcedType {
name = 'BOOLEAN'
includeExpression = 'deleted|enabled|is.*'
includeTypes = '.*'
}
forcedType {
name = 'varchar'
includeExpression = '.*'
includeTypes = 'INET'
}
}
}
generate {
pojos = true
}
target {
packageName = 'com.databasir.dao'
directory = 'generated-src/jooq/main/java'
}
strategy.name = 'com.databasir.dao.strategy.DatabasirPojoNamingStrategy'
}
}
}
}
}

159
dao/generated-src/jooq/main/java/com/databasir/dao/Databasir.java

@ -0,0 +1,159 @@
/*
* This file is generated by jOOQ.
*/
package com.databasir.dao;
import com.databasir.dao.tables.DataSource;
import com.databasir.dao.tables.DataSourceProperty;
import com.databasir.dao.tables.DatabaseDocument;
import com.databasir.dao.tables.DatabaseDocumentHistory;
import com.databasir.dao.tables.Group;
import com.databasir.dao.tables.Login;
import com.databasir.dao.tables.Project;
import com.databasir.dao.tables.ProjectSyncRule;
import com.databasir.dao.tables.SysKey;
import com.databasir.dao.tables.SysMail;
import com.databasir.dao.tables.TableColumnDocument;
import com.databasir.dao.tables.TableDocument;
import com.databasir.dao.tables.TableIndexDocument;
import com.databasir.dao.tables.TableTriggerDocument;
import com.databasir.dao.tables.User;
import com.databasir.dao.tables.UserRole;
import java.util.Arrays;
import java.util.List;
import org.jooq.Catalog;
import org.jooq.Table;
import org.jooq.impl.SchemaImpl;
/**
* This class is generated by jOOQ.
*/
@SuppressWarnings({ "all", "unchecked", "rawtypes" })
public class Databasir extends SchemaImpl {
private static final long serialVersionUID = 1L;
/**
* The reference instance of <code>databasir</code>
*/
public static final Databasir DATABASIR = new Databasir();
/**
* The table <code>databasir.data_source</code>.
*/
public final DataSource DATA_SOURCE = DataSource.DATA_SOURCE;
/**
* The table <code>databasir.data_source_property</code>.
*/
public final DataSourceProperty DATA_SOURCE_PROPERTY = DataSourceProperty.DATA_SOURCE_PROPERTY;
/**
* The table <code>databasir.database_document</code>.
*/
public final DatabaseDocument DATABASE_DOCUMENT = DatabaseDocument.DATABASE_DOCUMENT;
/**
* The table <code>databasir.database_document_history</code>.
*/
public final DatabaseDocumentHistory DATABASE_DOCUMENT_HISTORY = DatabaseDocumentHistory.DATABASE_DOCUMENT_HISTORY;
/**
* The table <code>databasir.group</code>.
*/
public final Group GROUP = Group.GROUP;
/**
* The table <code>databasir.login</code>.
*/
public final Login LOGIN = Login.LOGIN;
/**
* The table <code>databasir.project</code>.
*/
public final Project PROJECT = Project.PROJECT;
/**
* The table <code>databasir.project_sync_rule</code>.
*/
public final ProjectSyncRule PROJECT_SYNC_RULE = ProjectSyncRule.PROJECT_SYNC_RULE;
/**
* The table <code>databasir.sys_key</code>.
*/
public final SysKey SYS_KEY = SysKey.SYS_KEY;
/**
* The table <code>databasir.sys_mail</code>.
*/
public final SysMail SYS_MAIL = SysMail.SYS_MAIL;
/**
* The table <code>databasir.table_column_document</code>.
*/
public final TableColumnDocument TABLE_COLUMN_DOCUMENT = TableColumnDocument.TABLE_COLUMN_DOCUMENT;
/**
* The table <code>databasir.table_document</code>.
*/
public final TableDocument TABLE_DOCUMENT = TableDocument.TABLE_DOCUMENT;
/**
* The table <code>databasir.table_index_document</code>.
*/
public final TableIndexDocument TABLE_INDEX_DOCUMENT = TableIndexDocument.TABLE_INDEX_DOCUMENT;
/**
* The table <code>databasir.table_trigger_document</code>.
*/
public final TableTriggerDocument TABLE_TRIGGER_DOCUMENT = TableTriggerDocument.TABLE_TRIGGER_DOCUMENT;
/**
* The table <code>databasir.user</code>.
*/
public final User USER = User.USER;
/**
* The table <code>databasir.user_role</code>.
*/
public final UserRole USER_ROLE = UserRole.USER_ROLE;
/**
* No further instances allowed
*/
private Databasir() {
super("databasir", null);
}
@Override
public Catalog getCatalog() {
return DefaultCatalog.DEFAULT_CATALOG;
}
@Override
public final List<Table<?>> getTables() {
return Arrays.asList(
DataSource.DATA_SOURCE,
DataSourceProperty.DATA_SOURCE_PROPERTY,
DatabaseDocument.DATABASE_DOCUMENT,
DatabaseDocumentHistory.DATABASE_DOCUMENT_HISTORY,
Group.GROUP,
Login.LOGIN,
Project.PROJECT,
ProjectSyncRule.PROJECT_SYNC_RULE,
SysKey.SYS_KEY,
SysMail.SYS_MAIL,
TableColumnDocument.TABLE_COLUMN_DOCUMENT,
TableDocument.TABLE_DOCUMENT,
TableIndexDocument.TABLE_INDEX_DOCUMENT,
TableTriggerDocument.TABLE_TRIGGER_DOCUMENT,
User.USER,
UserRole.USER_ROLE
);
}
}

45
dao/generated-src/jooq/main/java/com/databasir/dao/DefaultCatalog.java

@ -0,0 +1,45 @@
/*
* This file is generated by jOOQ.
*/
package com.databasir.dao;
import java.util.Arrays;
import java.util.List;
import org.jooq.Schema;
import org.jooq.impl.CatalogImpl;
/**
* This class is generated by jOOQ.
*/
@SuppressWarnings({ "all", "unchecked", "rawtypes" })
public class DefaultCatalog extends CatalogImpl {
private static final long serialVersionUID = 1L;
/**
* The reference instance of <code>DEFAULT_CATALOG</code>
*/
public static final DefaultCatalog DEFAULT_CATALOG = new DefaultCatalog();
/**
* The schema <code>databasir</code>.
*/
public final Databasir DATABASIR = Databasir.DATABASIR;
/**
* No further instances allowed
*/
private DefaultCatalog() {
super("");
}
@Override
public final List<Schema> getSchemas() {
return Arrays.asList(
Databasir.DATABASIR
);
}
}

39
dao/generated-src/jooq/main/java/com/databasir/dao/Indexes.java

@ -0,0 +1,39 @@
/*
* This file is generated by jOOQ.
*/
package com.databasir.dao;
import com.databasir.dao.tables.DataSourceProperty;
import com.databasir.dao.tables.DatabaseDocumentHistory;
import com.databasir.dao.tables.TableColumnDocument;
import com.databasir.dao.tables.TableDocument;
import com.databasir.dao.tables.TableIndexDocument;
import com.databasir.dao.tables.TableTriggerDocument;
import org.jooq.Index;
import org.jooq.OrderField;
import org.jooq.impl.DSL;
import org.jooq.impl.Internal;
/**
* A class modelling indexes of tables in databasir.
*/
@SuppressWarnings({ "all", "unchecked", "rawtypes" })
public class Indexes {
// -------------------------------------------------------------------------
// INDEX definitions
// -------------------------------------------------------------------------
public static final Index DATA_SOURCE_PROPERTY_IDX_DATA_SOURCE_ID = Internal.createIndex(DSL.name("idx_data_source_id"), DataSourceProperty.DATA_SOURCE_PROPERTY, new OrderField[] { DataSourceProperty.DATA_SOURCE_PROPERTY.DATA_SOURCE_ID }, false);
public static final Index TABLE_COLUMN_DOCUMENT_IDX_DATABASE_DOCUMENT_ID = Internal.createIndex(DSL.name("idx_database_document_id"), TableColumnDocument.TABLE_COLUMN_DOCUMENT, new OrderField[] { TableColumnDocument.TABLE_COLUMN_DOCUMENT.DATABASE_DOCUMENT_ID }, false);
public static final Index TABLE_DOCUMENT_IDX_DATABASE_DOCUMENT_ID = Internal.createIndex(DSL.name("idx_database_document_id"), TableDocument.TABLE_DOCUMENT, new OrderField[] { TableDocument.TABLE_DOCUMENT.DATABASE_DOCUMENT_ID }, false);
public static final Index TABLE_INDEX_DOCUMENT_IDX_DATABASE_DOCUMENT_ID = Internal.createIndex(DSL.name("idx_database_document_id"), TableIndexDocument.TABLE_INDEX_DOCUMENT, new OrderField[] { TableIndexDocument.TABLE_INDEX_DOCUMENT.DATABASE_DOCUMENT_ID }, false);
public static final Index TABLE_TRIGGER_DOCUMENT_IDX_DATABASE_DOCUMENT_ID = Internal.createIndex(DSL.name("idx_database_document_id"), TableTriggerDocument.TABLE_TRIGGER_DOCUMENT, new OrderField[] { TableTriggerDocument.TABLE_TRIGGER_DOCUMENT.DATABASE_DOCUMENT_ID }, false);
public static final Index DATABASE_DOCUMENT_HISTORY_IDX_PROJECT_ID = Internal.createIndex(DSL.name("idx_project_id"), DatabaseDocumentHistory.DATABASE_DOCUMENT_HISTORY, new OrderField[] { DatabaseDocumentHistory.DATABASE_DOCUMENT_HISTORY.PROJECT_ID }, false);
public static final Index TABLE_COLUMN_DOCUMENT_IDX_TABLE_DOCUMENT_ID = Internal.createIndex(DSL.name("idx_table_document_id"), TableColumnDocument.TABLE_COLUMN_DOCUMENT, new OrderField[] { TableColumnDocument.TABLE_COLUMN_DOCUMENT.TABLE_DOCUMENT_ID }, false);
public static final Index TABLE_INDEX_DOCUMENT_IDX_TABLE_DOCUMENT_ID = Internal.createIndex(DSL.name("idx_table_document_id"), TableIndexDocument.TABLE_INDEX_DOCUMENT, new OrderField[] { TableIndexDocument.TABLE_INDEX_DOCUMENT.TABLE_DOCUMENT_ID }, false);
public static final Index TABLE_TRIGGER_DOCUMENT_IDX_TABLE_DOCUMENT_ID = Internal.createIndex(DSL.name("idx_table_document_id"), TableTriggerDocument.TABLE_TRIGGER_DOCUMENT, new OrderField[] { TableTriggerDocument.TABLE_TRIGGER_DOCUMENT.TABLE_DOCUMENT_ID }, false);
}

83
dao/generated-src/jooq/main/java/com/databasir/dao/Keys.java

@ -0,0 +1,83 @@
/*
* This file is generated by jOOQ.
*/
package com.databasir.dao;
import com.databasir.dao.tables.DataSource;
import com.databasir.dao.tables.DataSourceProperty;
import com.databasir.dao.tables.DatabaseDocument;
import com.databasir.dao.tables.DatabaseDocumentHistory;
import com.databasir.dao.tables.Group;
import com.databasir.dao.tables.Login;
import com.databasir.dao.tables.Project;
import com.databasir.dao.tables.ProjectSyncRule;
import com.databasir.dao.tables.SysKey;
import com.databasir.dao.tables.SysMail;
import com.databasir.dao.tables.TableColumnDocument;
import com.databasir.dao.tables.TableDocument;
import com.databasir.dao.tables.TableIndexDocument;
import com.databasir.dao.tables.TableTriggerDocument;
import com.databasir.dao.tables.User;
import com.databasir.dao.tables.UserRole;
import com.databasir.dao.tables.records.DataSourcePropertyRecord;
import com.databasir.dao.tables.records.DataSourceRecord;
import com.databasir.dao.tables.records.DatabaseDocumentHistoryRecord;
import com.databasir.dao.tables.records.DatabaseDocumentRecord;
import com.databasir.dao.tables.records.GroupRecord;
import com.databasir.dao.tables.records.LoginRecord;
import com.databasir.dao.tables.records.ProjectRecord;
import com.databasir.dao.tables.records.ProjectSyncRuleRecord;
import com.databasir.dao.tables.records.SysKeyRecord;
import com.databasir.dao.tables.records.SysMailRecord;
import com.databasir.dao.tables.records.TableColumnDocumentRecord;
import com.databasir.dao.tables.records.TableDocumentRecord;
import com.databasir.dao.tables.records.TableIndexDocumentRecord;
import com.databasir.dao.tables.records.TableTriggerDocumentRecord;
import com.databasir.dao.tables.records.UserRecord;
import com.databasir.dao.tables.records.UserRoleRecord;
import org.jooq.TableField;
import org.jooq.UniqueKey;
import org.jooq.impl.DSL;
import org.jooq.impl.Internal;
/**
* A class modelling foreign key relationships and constraints of tables in
* databasir.
*/
@SuppressWarnings({ "all", "unchecked", "rawtypes" })
public class Keys {
// -------------------------------------------------------------------------
// UNIQUE and PRIMARY KEY definitions
// -------------------------------------------------------------------------
public static final UniqueKey<DataSourceRecord> KEY_DATA_SOURCE_PRIMARY = Internal.createUniqueKey(DataSource.DATA_SOURCE, DSL.name("KEY_data_source_PRIMARY"), new TableField[] { DataSource.DATA_SOURCE.ID }, true);
public static final UniqueKey<DataSourceRecord> KEY_DATA_SOURCE_UK_PROJECT_ID = Internal.createUniqueKey(DataSource.DATA_SOURCE, DSL.name("KEY_data_source_uk_project_id"), new TableField[] { DataSource.DATA_SOURCE.PROJECT_ID }, true);
public static final UniqueKey<DataSourcePropertyRecord> KEY_DATA_SOURCE_PROPERTY_PRIMARY = Internal.createUniqueKey(DataSourceProperty.DATA_SOURCE_PROPERTY, DSL.name("KEY_data_source_property_PRIMARY"), new TableField[] { DataSourceProperty.DATA_SOURCE_PROPERTY.ID }, true);
public static final UniqueKey<DatabaseDocumentRecord> KEY_DATABASE_DOCUMENT_PRIMARY = Internal.createUniqueKey(DatabaseDocument.DATABASE_DOCUMENT, DSL.name("KEY_database_document_PRIMARY"), new TableField[] { DatabaseDocument.DATABASE_DOCUMENT.ID }, true);
public static final UniqueKey<DatabaseDocumentRecord> KEY_DATABASE_DOCUMENT_UK_PROJECT_ID = Internal.createUniqueKey(DatabaseDocument.DATABASE_DOCUMENT, DSL.name("KEY_database_document_uk_project_id"), new TableField[] { DatabaseDocument.DATABASE_DOCUMENT.PROJECT_ID }, true);
public static final UniqueKey<DatabaseDocumentHistoryRecord> KEY_DATABASE_DOCUMENT_HISTORY_PRIMARY = Internal.createUniqueKey(DatabaseDocumentHistory.DATABASE_DOCUMENT_HISTORY, DSL.name("KEY_database_document_history_PRIMARY"), new TableField[] { DatabaseDocumentHistory.DATABASE_DOCUMENT_HISTORY.ID }, true);
public static final UniqueKey<DatabaseDocumentHistoryRecord> KEY_DATABASE_DOCUMENT_HISTORY_UK_CONNECTION_ID_VERSION = Internal.createUniqueKey(DatabaseDocumentHistory.DATABASE_DOCUMENT_HISTORY, DSL.name("KEY_database_document_history_uk_connection_id_version"), new TableField[] { DatabaseDocumentHistory.DATABASE_DOCUMENT_HISTORY.DATABASE_DOCUMENT_ID, DatabaseDocumentHistory.DATABASE_DOCUMENT_HISTORY.VERSION }, true);
public static final UniqueKey<GroupRecord> KEY_GROUP_PRIMARY = Internal.createUniqueKey(Group.GROUP, DSL.name("KEY_group_PRIMARY"), new TableField[] { Group.GROUP.ID }, true);
public static final UniqueKey<GroupRecord> KEY_GROUP_UK_NAME = Internal.createUniqueKey(Group.GROUP, DSL.name("KEY_group_uk_name"), new TableField[] { Group.GROUP.NAME }, true);
public static final UniqueKey<LoginRecord> KEY_LOGIN_PRIMARY = Internal.createUniqueKey(Login.LOGIN, DSL.name("KEY_login_PRIMARY"), new TableField[] { Login.LOGIN.ID }, true);
public static final UniqueKey<LoginRecord> KEY_LOGIN_UK_USER_ID = Internal.createUniqueKey(Login.LOGIN, DSL.name("KEY_login_uk_user_id"), new TableField[] { Login.LOGIN.USER_ID }, true);
public static final UniqueKey<ProjectRecord> KEY_PROJECT_PRIMARY = Internal.createUniqueKey(Project.PROJECT, DSL.name("KEY_project_PRIMARY"), new TableField[] { Project.PROJECT.ID }, true);
public static final UniqueKey<ProjectRecord> KEY_PROJECT_UK_GROUP_ID_NAME = Internal.createUniqueKey(Project.PROJECT, DSL.name("KEY_project_uk_group_id_name"), new TableField[] { Project.PROJECT.GROUP_ID, Project.PROJECT.NAME }, true);
public static final UniqueKey<ProjectSyncRuleRecord> KEY_PROJECT_SYNC_RULE_PRIMARY = Internal.createUniqueKey(ProjectSyncRule.PROJECT_SYNC_RULE, DSL.name("KEY_project_sync_rule_PRIMARY"), new TableField[] { ProjectSyncRule.PROJECT_SYNC_RULE.ID }, true);
public static final UniqueKey<ProjectSyncRuleRecord> KEY_PROJECT_SYNC_RULE_UK_PROJECT_ID = Internal.createUniqueKey(ProjectSyncRule.PROJECT_SYNC_RULE, DSL.name("KEY_project_sync_rule_uk_project_id"), new TableField[] { ProjectSyncRule.PROJECT_SYNC_RULE.PROJECT_ID }, true);
public static final UniqueKey<SysKeyRecord> KEY_SYS_KEY_PRIMARY = Internal.createUniqueKey(SysKey.SYS_KEY, DSL.name("KEY_sys_key_PRIMARY"), new TableField[] { SysKey.SYS_KEY.ID }, true);
public static final UniqueKey<SysMailRecord> KEY_SYS_MAIL_PRIMARY = Internal.createUniqueKey(SysMail.SYS_MAIL, DSL.name("KEY_sys_mail_PRIMARY"), new TableField[] { SysMail.SYS_MAIL.ID }, true);
public static final UniqueKey<TableColumnDocumentRecord> KEY_TABLE_COLUMN_DOCUMENT_PRIMARY = Internal.createUniqueKey(TableColumnDocument.TABLE_COLUMN_DOCUMENT, DSL.name("KEY_table_column_document_PRIMARY"), new TableField[] { TableColumnDocument.TABLE_COLUMN_DOCUMENT.ID }, true);
public static final UniqueKey<TableDocumentRecord> KEY_TABLE_DOCUMENT_PRIMARY = Internal.createUniqueKey(TableDocument.TABLE_DOCUMENT, DSL.name("KEY_table_document_PRIMARY"), new TableField[] { TableDocument.TABLE_DOCUMENT.ID }, true);
public static final UniqueKey<TableIndexDocumentRecord> KEY_TABLE_INDEX_DOCUMENT_PRIMARY = Internal.createUniqueKey(TableIndexDocument.TABLE_INDEX_DOCUMENT, DSL.name("KEY_table_index_document_PRIMARY"), new TableField[] { TableIndexDocument.TABLE_INDEX_DOCUMENT.ID }, true);
public static final UniqueKey<TableTriggerDocumentRecord> KEY_TABLE_TRIGGER_DOCUMENT_PRIMARY = Internal.createUniqueKey(TableTriggerDocument.TABLE_TRIGGER_DOCUMENT, DSL.name("KEY_table_trigger_document_PRIMARY"), new TableField[] { TableTriggerDocument.TABLE_TRIGGER_DOCUMENT.ID }, true);
public static final UniqueKey<UserRecord> KEY_USER_PRIMARY = Internal.createUniqueKey(User.USER, DSL.name("KEY_user_PRIMARY"), new TableField[] { User.USER.ID }, true);
public static final UniqueKey<UserRecord> KEY_USER_UK_EMAIL = Internal.createUniqueKey(User.USER, DSL.name("KEY_user_uk_email"), new TableField[] { User.USER.EMAIL }, true);
public static final UniqueKey<UserRecord> KEY_USER_UK_USERNAME = Internal.createUniqueKey(User.USER, DSL.name("KEY_user_uk_username"), new TableField[] { User.USER.USERNAME }, true);
public static final UniqueKey<UserRoleRecord> KEY_USER_ROLE_PRIMARY = Internal.createUniqueKey(UserRole.USER_ROLE, DSL.name("KEY_user_role_PRIMARY"), new TableField[] { UserRole.USER_ROLE.ID }, true);
public static final UniqueKey<UserRoleRecord> KEY_USER_ROLE_UK_USER_ID_GROUP_ID_ROLE = Internal.createUniqueKey(UserRole.USER_ROLE, DSL.name("KEY_user_role_uk_user_id_group_id_role"), new TableField[] { UserRole.USER_ROLE.USER_ID, UserRole.USER_ROLE.GROUP_ID, UserRole.USER_ROLE.ROLE }, true);
}

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save