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 { |
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.commonmark:commonmark:0.18.1' |
||||
implementation 'org.freemarker:freemarker:2.3.31' |
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