vran
3 years ago
committed by
GitHub
205 changed files with 17366 additions and 59 deletions
@ -0,0 +1,5 @@ |
|||
rootProject.name = 'api' |
|||
include 'plugin' |
|||
include 'common' |
|||
include 'dao' |
|||
include 'core' |
@ -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"] |
@ -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 |
@ -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); |
|||
} |
|||
} |
@ -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)); |
|||
} |
|||
|
|||
} |
@ -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(); |
|||
} |
|||
|
|||
} |
@ -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; |
|||
} |
|||
} |
|||
} |
@ -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)); |
|||
} |
|||
} |
@ -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"; |
|||
} |
|||
} |
@ -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(); |
|||
} |
|||
} |
@ -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(); |
|||
} |
|||
} |
|||
} |
@ -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; |
|||
} |
|||
} |
@ -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(); |
|||
} |
|||
|
|||
} |
@ -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"); |
|||
} |
|||
} |
@ -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()); |
|||
} |
|||
|
|||
} |
@ -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)); |
|||
} |
|||
} |
|||
} |
@ -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)); |
|||
} |
|||
} |
@ -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); |
|||
} |
|||
} |
@ -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); |
|||
} |
|||
} |
@ -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(); |
|||
} |
|||
} |
@ -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); |
|||
} |
|||
|
|||
} |
@ -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 |
@ -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 |
@ -0,0 +1,3 @@ |
|||
dependencies { |
|||
implementation 'org.apache.shiro:shiro-crypto-cipher:1.8.0' |
|||
} |
@ -0,0 +1,8 @@ |
|||
package com.databasir.common; |
|||
|
|||
public interface DatabasirErrors { |
|||
|
|||
String getErrCode(); |
|||
|
|||
String getErrMessage(); |
|||
} |
@ -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(); |
|||
} |
|||
} |
@ -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; |
|||
} |
|||
} |
@ -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); |
|||
} |
|||
} |
@ -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); |
|||
} |
|||
|
|||
} |
@ -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; |
|||
} |
|||
|
|||
} |
@ -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); |
|||
} |
|||
|
|||
} |
@ -0,0 +1,4 @@ |
|||
package com.databasir.common.exception; |
|||
|
|||
public class Forbidden extends RuntimeException{ |
|||
} |
@ -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(); |
|||
} |
|||
} |
@ -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); |
|||
} |
|||
} |
@ -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}" |
|||
} |
|||
|
|||
|
@ -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); |
|||
} |
|||
} |
@ -0,0 +1,9 @@ |
|||
package com.databasir.core.domain.document.converter; |
|||
|
|||
public interface BaseConverter { |
|||
|
|||
@NullToEmpty |
|||
default String nullToEmpty(String s) { |
|||
return s == null ? "" : s; |
|||
} |
|||
} |
@ -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); |
|||
} |
@ -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); |
|||
|
|||
} |
@ -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); |
|||
} |
@ -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 { |
|||
} |
@ -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; |
|||
} |
|||
} |
|||
} |
@ -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; |
|||
|
|||
} |
@ -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); |
|||
} |
|||
} |
@ -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); |
|||
} |
@ -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); |
|||
} |
@ -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<>(); |
|||
|
|||
} |
@ -0,0 +1,12 @@ |
|||
package com.databasir.core.domain.group.data; |
|||
|
|||
import lombok.Data; |
|||
|
|||
@Data |
|||
public class GroupMemberCreateRequest { |
|||
|
|||
private Integer userId; |
|||
|
|||
private String role; |
|||
|
|||
} |
@ -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()); |
|||
} |
|||
} |
@ -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; |
|||
} |
@ -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; |
|||
|
|||
} |
@ -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(); |
|||
} |
|||
} |
@ -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; |
|||
|
|||
} |
@ -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; |
|||
|
|||
} |
|||
} |
@ -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<>(); |
|||
|
|||
} |
@ -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); |
|||
} |
|||
} |
|||
} |
@ -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; |
|||
|
|||
} |
@ -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; |
|||
} |
@ -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()); |
|||
} |
|||
} |
@ -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(); |
|||
} |
|||
} |
@ -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()); |
|||
} |
|||
|
|||
} |
@ -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); |
|||
} |
@ -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); |
|||
} |
@ -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; |
|||
|
|||
} |
@ -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<>(); |
|||
|
|||
} |
|||
|
|||
} |
@ -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; |
|||
} |
|||
} |
@ -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()); |
|||
} |
|||
} |
@ -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; |
|||
|
|||
} |
@ -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<>(); |
|||
|
|||
} |
|||
} |
@ -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); |
|||
}); |
|||
} |
|||
|
|||
} |
@ -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; |
|||
} |
@ -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; |
|||
|
|||
} |
@ -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); |
|||
} |
|||
} |
|||
} |
@ -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); |
|||
} |
@ -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); |
|||
|
|||
} |
@ -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; |
|||
|
|||
} |
@ -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; |
|||
} |
|||
} |
@ -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; |
|||
|
|||
} |
@ -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; |
|||
} |
|||
} |
|||
|
@ -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; |
|||
|
|||
} |
@ -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()); |
|||
} |
|||
} |
@ -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; |
|||
} |
@ -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; |
|||
|
|||
} |
@ -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; |
|||
} |
@ -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); |
|||
} |
|||
} |
@ -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); |
|||
} |
@ -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); |
|||
} |
|||
} |
@ -0,0 +1,10 @@ |
|||
package com.databasir.core.infrastructure.connection; |
|||
|
|||
public interface DatabaseTypes { |
|||
|
|||
String MYSQL = "mysql"; |
|||
|
|||
String POSTGRESQL = "postgresql"; |
|||
|
|||
String ORACLE = "oracle"; |
|||
} |
@ -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); |
|||
} |
|||
} |
|||
|
|||
} |
@ -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); |
|||
} |
|||
} |
|||
} |
@ -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); |
|||
} |
|||
} |
|||
} |
@ -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(); |
|||
} |
|||
} |
@ -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; |
|||
} |
|||
} |
@ -0,0 +1,5 @@ |
|||
package com.databasir.core.infrastructure.meta; |
|||
|
|||
public class DatabaseMetaResolver { |
|||
|
|||
} |
@ -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' |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
@ -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 |
|||
); |
|||
} |
|||
} |
@ -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 |
|||
); |
|||
} |
|||
} |
@ -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); |
|||
} |
@ -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…
Reference in new issue