feat: init api (#2)

This commit is contained in:
vran
2022-01-24 22:58:47 +08:00
committed by GitHub
parent 643d182d5f
commit 61e5708196
205 changed files with 17366 additions and 59 deletions

View File

@@ -0,0 +1,24 @@
package com.databasir.dao.exception;
public class DataNotExistsException extends RuntimeException {
public DataNotExistsException() {
super();
}
public DataNotExistsException(String message) {
super(message);
}
public DataNotExistsException(String message, Throwable cause) {
super(message, cause);
}
public DataNotExistsException(Throwable cause) {
super(cause);
}
protected DataNotExistsException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}

View File

@@ -0,0 +1,123 @@
package com.databasir.dao.impl;
import com.databasir.dao.exception.DataNotExistsException;
import lombok.RequiredArgsConstructor;
import org.jooq.*;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import java.util.*;
import java.util.stream.Collectors;
@RequiredArgsConstructor
public abstract class BaseDao<T extends Record, R> {
private final Table<T> table;
private final Class<R> pojoType;
public abstract DSLContext getDslContext();
public boolean existsById(Integer id) {
return getDslContext().fetchExists(table, identity().eq(id));
}
public Integer insertAndReturnId(R pojo) {
T record = getDslContext().newRecord(table, pojo);
UpdatableRecord<?> updatableRecord = (UpdatableRecord<?>) record;
updatableRecord.store();
Object value = updatableRecord.getValue(table.getIdentity().getField());
return (Integer) value;
}
public int batchInsert(Collection<R> pojoList) {
List<TableRecord<?>> records = pojoList.stream()
.map(pojo -> {
T record = getDslContext().newRecord(table, pojo);
return (TableRecord<?>) record;
})
.collect(Collectors.toList());
return Arrays.stream(getDslContext().batchInsert(records).execute()).sum();
}
public int deleteById(Integer id) {
return getDslContext()
.deleteFrom(table).where(identity().eq(id))
.execute();
}
public int updateById(R pojo) {
T record = getDslContext().newRecord(table, pojo);
record.changed(table.getIdentity().getField(), false);
return getDslContext().executeUpdate((UpdatableRecord<?>) record);
}
public Optional<R> selectOptionalById(Integer id) {
return getDslContext()
.select(table.fields()).from(table).where(identity().eq(id))
.fetchOptionalInto(pojoType);
}
public R selectById(Integer id) {
return selectOptionalById(id)
.orElseThrow(() -> new DataNotExistsException("data not exists in " + table.getName() + " with id = " + id));
}
public List<R> selectInIds(List<Integer> ids) {
if (ids == null || ids.isEmpty()) {
return Collections.emptyList();
}
return getDslContext()
.select(table().fields()).from(table())
.where(identity().in(ids))
.fetchInto(pojoType);
}
public Page<R> selectByPage(Pageable request, Condition condition) {
Integer count = getDslContext()
.selectCount().from(table).where(condition)
.fetchOne(0, int.class);
int total = count == null ? 0 : count;
List<R> data = getDslContext()
.selectFrom(table).where(condition)
.orderBy(getSortFields(request.getSort()))
.offset(request.getOffset()).limit(request.getPageSize())
.fetchInto(pojoType);
return new PageImpl<>(data, request, total);
}
protected Collection<SortField<?>> getSortFields(Sort sortSpecification) {
Collection<SortField<?>> querySortFields = new ArrayList<>();
if (sortSpecification == null) {
return querySortFields;
}
Iterator<Sort.Order> specifiedFields = sortSpecification.iterator();
while (specifiedFields.hasNext()) {
Sort.Order specifiedField = specifiedFields.next();
String sortFieldName = specifiedField.getProperty();
Field<?> field = table().field(sortFieldName);
TableField tableField = (TableField) field;
if (specifiedField.getDirection() == Sort.Direction.ASC) {
querySortFields.add(tableField.asc());
} else {
querySortFields.add(tableField.desc());
}
}
return querySortFields;
}
protected Field<Integer> identity() {
Identity<T, ?> identity = table.getIdentity();
if (identity == null) {
throw new IllegalStateException("can not find identity column in " + table.getName());
}
return identity.getField().cast(Integer.class);
}
protected Table<T> table() {
return this.table;
}
}

View File

@@ -0,0 +1,62 @@
package com.databasir.dao.impl;
import com.databasir.dao.exception.DataNotExistsException;
import com.databasir.dao.tables.pojos.DataSourcePojo;
import com.databasir.dao.tables.records.DataSourceRecord;
import lombok.Getter;
import org.jooq.DSLContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import static com.databasir.dao.Tables.DATA_SOURCE;
@Repository
public class DataSourceDao extends BaseDao<DataSourceRecord, DataSourcePojo> {
@Autowired
@Getter
private DSLContext dslContext;
public DataSourceDao() {
super(DATA_SOURCE, DataSourcePojo.class);
}
public Optional<DataSourcePojo> selectOptionalByProjectId(Integer projectId) {
return getDslContext()
.select(DATA_SOURCE.fields()).from(DATA_SOURCE).where(DATA_SOURCE.PROJECT_ID.eq(projectId))
.fetchOptionalInto(DataSourcePojo.class);
}
public DataSourcePojo selectByProjectId(Integer projectId) {
return getDslContext()
.select(DATA_SOURCE.fields()).from(DATA_SOURCE).where(DATA_SOURCE.PROJECT_ID.eq(projectId))
.fetchOptionalInto(DataSourcePojo.class)
.orElseThrow(() -> new DataNotExistsException("data not exists in " + table().getName() + " with schemaSourceId = " + projectId));
}
public int updateByProjectId(DataSourcePojo dataSource) {
DataSourceRecord record = getDslContext().newRecord(DATA_SOURCE, dataSource);
record.changed(DATA_SOURCE.ID, false);
record.changed(DATA_SOURCE.PROJECT_ID, false);
if (dataSource.getPassword() == null || dataSource.getPassword().trim().equals("")) {
record.changed(DATA_SOURCE.PASSWORD, false);
}
return getDslContext()
.update(DATA_SOURCE).set(record).where(DATA_SOURCE.PROJECT_ID.eq(dataSource.getProjectId()))
.execute();
}
public List<DataSourcePojo> selectInProjectIds(List<Integer> projectIds) {
if (projectIds == null || projectIds.isEmpty()) {
return Collections.emptyList();
}
return getDslContext()
.select(DATA_SOURCE.fields()).from(DATA_SOURCE).where(DATA_SOURCE.PROJECT_ID.in(projectIds))
.fetchInto(DataSourcePojo.class);
}
}

View File

@@ -0,0 +1,38 @@
package com.databasir.dao.impl;
import com.databasir.dao.tables.pojos.DataSourcePropertyPojo;
import com.databasir.dao.tables.records.DataSourcePropertyRecord;
import lombok.Getter;
import org.jooq.DSLContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import java.util.List;
import static com.databasir.dao.Tables.DATA_SOURCE_PROPERTY;
@Repository
public class DataSourcePropertyDao extends BaseDao<DataSourcePropertyRecord, DataSourcePropertyPojo> {
@Autowired
@Getter
private DSLContext dslContext;
public DataSourcePropertyDao() {
super(DATA_SOURCE_PROPERTY, DataSourcePropertyPojo.class);
}
public int deleteByDataSourceId(Integer dataSourceId) {
return dslContext
.deleteFrom(DATA_SOURCE_PROPERTY).where(DATA_SOURCE_PROPERTY.DATA_SOURCE_ID.eq(dataSourceId))
.execute();
}
public List<DataSourcePropertyPojo> selectByDataSourceId(Integer id) {
return dslContext
.select(DATA_SOURCE_PROPERTY.fields()).from(DATA_SOURCE_PROPERTY)
.where(DATA_SOURCE_PROPERTY.DATA_SOURCE_ID.eq(id))
.fetchInto(DataSourcePropertyPojo.class);
}
}

View File

@@ -0,0 +1,38 @@
package com.databasir.dao.impl;
import com.databasir.dao.tables.pojos.DatabaseDocumentPojo;
import com.databasir.dao.tables.records.DatabaseDocumentRecord;
import lombok.Getter;
import org.jooq.DSLContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import java.util.Optional;
import static com.databasir.dao.Tables.DATABASE_DOCUMENT;
@Repository
public class DatabaseDocumentDao extends BaseDao<DatabaseDocumentRecord, DatabaseDocumentPojo> {
@Autowired
@Getter
private DSLContext dslContext;
public DatabaseDocumentDao() {
super(DATABASE_DOCUMENT, DatabaseDocumentPojo.class);
}
public Optional<DatabaseDocumentPojo> selectOptionalByProjectId(Integer projectId) {
return getDslContext()
.select(DATABASE_DOCUMENT.fields()).from(DATABASE_DOCUMENT).where(DATABASE_DOCUMENT.PROJECT_ID.eq(projectId))
.fetchOptionalInto(DatabaseDocumentPojo.class);
}
public void update(DatabaseDocumentPojo toPojo) {
DatabaseDocumentRecord record = getDslContext().newRecord(DATABASE_DOCUMENT, toPojo);
record.changed(DATABASE_DOCUMENT.ID, false);
record.changed(DATABASE_DOCUMENT.CREATE_AT, false);
record.update();
}
}

View File

@@ -0,0 +1,38 @@
package com.databasir.dao.impl;
import com.databasir.dao.tables.pojos.DatabaseDocumentHistoryPojo;
import com.databasir.dao.tables.records.DatabaseDocumentHistoryRecord;
import lombok.Getter;
import org.jooq.DSLContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Repository;
import java.util.Optional;
import static com.databasir.dao.Tables.DATABASE_DOCUMENT_HISTORY;
@Repository
public class DatabaseDocumentHistoryDao extends BaseDao<DatabaseDocumentHistoryRecord, DatabaseDocumentHistoryPojo> {
@Autowired
@Getter
private DSLContext dslContext;
public DatabaseDocumentHistoryDao() {
super(DATABASE_DOCUMENT_HISTORY, DatabaseDocumentHistoryPojo.class);
}
public Optional<DatabaseDocumentHistoryPojo> selectOptionalByProjectIdAndVersion(Integer projectId, Long version) {
return dslContext
.selectFrom(DATABASE_DOCUMENT_HISTORY).where(DATABASE_DOCUMENT_HISTORY.PROJECT_ID.eq(projectId).and(DATABASE_DOCUMENT_HISTORY.VERSION.eq(version)))
.fetchOptionalInto(DatabaseDocumentHistoryPojo.class);
}
public Page<DatabaseDocumentHistoryPojo> selectPageByDatabaseDocumentId(Pageable request, Integer schemaDocumentId) {
return super.selectByPage(request, DATABASE_DOCUMENT_HISTORY.DATABASE_DOCUMENT_ID.eq(schemaDocumentId));
}
}

View File

@@ -0,0 +1,59 @@
package com.databasir.dao.impl;
import com.databasir.dao.tables.pojos.GroupPojo;
import com.databasir.dao.tables.records.GroupRecord;
import lombok.Getter;
import org.jooq.Condition;
import org.jooq.DSLContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Repository;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import static com.databasir.dao.Tables.GROUP;
@Repository
public class GroupDao extends BaseDao<GroupRecord, GroupPojo> {
@Autowired
@Getter
private DSLContext dslContext;
public GroupDao() {
super(GROUP, GroupPojo.class);
}
@Override
public int deleteById(Integer id) {
return dslContext
.update(table()).set(GROUP.DELETED, true).where(GROUP.ID.eq(id))
.execute();
}
@Override
public Page<GroupPojo> selectByPage(Pageable request, Condition condition) {
return super.selectByPage(request, condition.and(GROUP.DELETED.eq(false)));
}
@Override
public Optional<GroupPojo> selectOptionalById(Integer id) {
return getDslContext()
.select(GROUP.fields()).from(GROUP).where(GROUP.ID.eq(id).and(GROUP.DELETED.eq(false)))
.fetchOptionalInto(GroupPojo.class);
}
@Override
public List<GroupPojo> selectInIds(List<Integer> ids) {
if (ids == null || ids.isEmpty()) {
return Collections.emptyList();
}
return getDslContext()
.select(GROUP.fields()).from(GROUP)
.where(GROUP.ID.in(ids)).and(GROUP.DELETED.eq(false))
.fetchInto(GroupPojo.class);
}
}

View File

@@ -0,0 +1,69 @@
package com.databasir.dao.impl;
import com.databasir.dao.tables.pojos.LoginPojo;
import com.databasir.dao.tables.records.LoginRecord;
import lombok.Getter;
import org.jooq.DSLContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import java.time.LocalDateTime;
import java.util.Optional;
import static com.databasir.dao.Tables.LOGIN;
@Repository
public class LoginDao extends BaseDao<LoginRecord, LoginPojo> {
@Autowired
@Getter
private DSLContext dslContext;
public LoginDao() {
super(LOGIN, LoginPojo.class);
}
public void deleteByUserId(Integer userId) {
getDslContext()
.deleteFrom(LOGIN).where(LOGIN.USER_ID.eq(userId))
.execute();
}
public Optional<LoginPojo> selectByUserId(Integer userId) {
return getDslContext()
.select(LOGIN.fields()).from(LOGIN).where(LOGIN.USER_ID.eq(userId))
.fetchOptionalInto(LoginPojo.class);
}
public Optional<LoginPojo> selectByRefreshToken(String refreshToken) {
return getDslContext()
.select(LOGIN.fields()).from(LOGIN).where(LOGIN.REFRESH_TOKEN.eq(refreshToken))
.fetchOptionalInto(LoginPojo.class);
}
public void updateAccessToken(String accessToken, LocalDateTime accessTokenExpireAt, Integer userId) {
getDslContext()
.update(LOGIN)
.set(LOGIN.ACCESS_TOKEN, accessToken)
.set(LOGIN.ACCESS_TOKEN_EXPIRE_AT, accessTokenExpireAt)
.where(LOGIN.USER_ID.eq(userId))
.execute();
}
public void insertOnDuplicateKeyUpdate(LoginPojo loginPojo) {
getDslContext()
.insertInto(LOGIN)
.set(LOGIN.USER_ID, loginPojo.getUserId())
.set(LOGIN.ACCESS_TOKEN, loginPojo.getAccessToken())
.set(LOGIN.ACCESS_TOKEN_EXPIRE_AT, loginPojo.getAccessTokenExpireAt())
.set(LOGIN.REFRESH_TOKEN, loginPojo.getRefreshToken())
.set(LOGIN.REFRESH_TOKEN_EXPIRE_AT, loginPojo.getRefreshTokenExpireAt())
.onDuplicateKeyUpdate()
.set(LOGIN.ACCESS_TOKEN, loginPojo.getAccessToken())
.set(LOGIN.ACCESS_TOKEN_EXPIRE_AT, loginPojo.getAccessTokenExpireAt())
.set(LOGIN.REFRESH_TOKEN, loginPojo.getRefreshToken())
.set(LOGIN.REFRESH_TOKEN_EXPIRE_AT, loginPojo.getRefreshTokenExpireAt())
.execute();
}
}

View File

@@ -0,0 +1,89 @@
package com.databasir.dao.impl;
import com.databasir.dao.tables.pojos.ProjectPojo;
import com.databasir.dao.tables.records.ProjectRecord;
import com.databasir.dao.value.GroupProjectCountPojo;
import lombok.Getter;
import org.jooq.Condition;
import org.jooq.DSLContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Repository;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import static com.databasir.dao.Tables.DATA_SOURCE;
import static com.databasir.dao.Tables.PROJECT;
@Repository
public class ProjectDao extends BaseDao<ProjectRecord, ProjectPojo> {
@Autowired
@Getter
private DSLContext dslContext;
public ProjectDao() {
super(PROJECT, ProjectPojo.class);
}
public int updateDeletedById(boolean b, Integer databaseId) {
return dslContext
.update(PROJECT).set(PROJECT.DELETED, b).where(PROJECT.ID.eq(databaseId))
.execute();
}
@Override
public Optional<ProjectPojo> selectOptionalById(Integer id) {
return getDslContext()
.select(PROJECT.fields()).from(PROJECT)
.where(identity().eq(id).and(PROJECT.DELETED.eq(false)))
.fetchOptionalInto(ProjectPojo.class);
}
@Override
public boolean existsById(Integer id) {
return getDslContext().fetchExists(table(), identity().eq(id).and(PROJECT.DELETED.eq(false)));
}
public boolean exists(Integer groupId, Integer projectId) {
return getDslContext()
.fetchExists(table(), identity().eq(projectId)
.and(PROJECT.GROUP_ID.eq(groupId))
.and(PROJECT.DELETED.eq(false)));
}
public Page<ProjectPojo> selectByCondition(Pageable request, Condition condition) {
int total = getDslContext()
.selectCount().from(PROJECT)
.innerJoin(DATA_SOURCE).on(DATA_SOURCE.PROJECT_ID.eq(PROJECT.ID))
.where(condition)
.fetchOne(0, int.class);
List<ProjectPojo> data = getDslContext()
.select(PROJECT.fields()).from(PROJECT)
.innerJoin(DATA_SOURCE).on(DATA_SOURCE.PROJECT_ID.eq(PROJECT.ID))
.where(condition)
.orderBy(getSortFields(request.getSort()))
.offset(request.getOffset()).limit(request.getPageSize())
.fetchInto(ProjectPojo.class);
return new PageImpl<>(data, request, total);
}
public List<GroupProjectCountPojo> selectCountByGroupIds(List<Integer> groupIds) {
if (groupIds == null || groupIds.isEmpty()) {
return Collections.emptyList();
}
String groupIdIn = groupIds.stream()
.map(Object::toString)
.collect(Collectors.joining(",", "(", ")"));
String sql = "select `group`.id group_id, count(project.id) as count from project "
+ " inner join `group` on `group`.id = project.group_id "
+ " where project.deleted = false and `group`.id in " + groupIdIn + " group by `group`.id;";
return dslContext.resultQuery(sql).fetchInto(GroupProjectCountPojo.class);
}
}

View File

@@ -0,0 +1,49 @@
package com.databasir.dao.impl;
import com.databasir.dao.exception.DataNotExistsException;
import com.databasir.dao.tables.ProjectSyncRule;
import com.databasir.dao.tables.pojos.ProjectSyncRulePojo;
import com.databasir.dao.tables.records.ProjectSyncRuleRecord;
import lombok.Getter;
import org.jooq.DSLContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import java.util.Optional;
import static com.databasir.dao.Tables.PROJECT_SYNC_RULE;
@Repository
public class ProjectSyncRuleDao extends BaseDao<ProjectSyncRuleRecord, ProjectSyncRulePojo> {
@Autowired
@Getter
private DSLContext dslContext;
public ProjectSyncRuleDao() {
super(PROJECT_SYNC_RULE, ProjectSyncRulePojo.class);
}
public Optional<ProjectSyncRulePojo> selectOptionalByProjectId(Integer projectId) {
return getDslContext()
.select(PROJECT_SYNC_RULE.fields()).from(PROJECT_SYNC_RULE).where(PROJECT_SYNC_RULE.PROJECT_ID.eq(projectId))
.fetchOptionalInto(ProjectSyncRulePojo.class);
}
public ProjectSyncRulePojo selectByProjectId(Integer projectId) {
return getDslContext()
.select(PROJECT_SYNC_RULE.fields()).from(PROJECT_SYNC_RULE).where(PROJECT_SYNC_RULE.PROJECT_ID.eq(projectId))
.fetchOptionalInto(ProjectSyncRulePojo.class)
.orElseThrow(() -> new DataNotExistsException("data not exists in " + table().getName() + " with projectId = " + projectId));
}
public int updateByProjectId(ProjectSyncRulePojo rule) {
ProjectSyncRule table = PROJECT_SYNC_RULE;
ProjectSyncRuleRecord record = getDslContext().newRecord(table, rule);
record.changed(table.ID, false);
record.changed(table.PROJECT_ID, false);
return getDslContext()
.update(table).set(record).where(table.PROJECT_ID.eq(rule.getProjectId()))
.execute();
}
}

View File

@@ -0,0 +1,36 @@
package com.databasir.dao.impl;
import com.databasir.dao.exception.DataNotExistsException;
import com.databasir.dao.tables.pojos.SysKeyPojo;
import com.databasir.dao.tables.records.SysKeyRecord;
import lombok.Getter;
import org.jooq.DSLContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import java.util.Optional;
import static com.databasir.dao.Tables.SYS_KEY;
@Repository
public class SysKeyDao extends BaseDao<SysKeyRecord, SysKeyPojo> {
@Autowired
@Getter
private DSLContext dslContext;
public SysKeyDao() {
super(SYS_KEY, SysKeyPojo.class);
}
public Optional<SysKeyPojo> selectOptionTopOne() {
return dslContext.select(SYS_KEY.fields()).from(SYS_KEY)
.limit(1)
.fetchOptionalInto(SysKeyPojo.class);
}
public SysKeyPojo selectTopOne() {
return selectOptionTopOne()
.orElseThrow(() -> new DataNotExistsException("no syskey data find"));
}
}

View File

@@ -0,0 +1,46 @@
package com.databasir.dao.impl;
import com.databasir.dao.exception.DataNotExistsException;
import com.databasir.dao.tables.pojos.SysMailPojo;
import com.databasir.dao.tables.records.SysMailRecord;
import lombok.Getter;
import org.jooq.DSLContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import java.util.Optional;
import static com.databasir.dao.Tables.SYS_MAIL;
@Repository
public class SysMailDao extends BaseDao<SysMailRecord, SysMailPojo> {
@Autowired
@Getter
private DSLContext dslContext;
public SysMailDao() {
super(SYS_MAIL, SysMailPojo.class);
}
public Optional<SysMailPojo> selectOptionTopOne() {
return dslContext.select(SYS_MAIL.fields()).from(SYS_MAIL)
.limit(1)
.fetchOptionalInto(SysMailPojo.class);
}
public SysMailPojo selectTopOne() {
return selectOptionTopOne()
.orElseThrow(() -> new DataNotExistsException("no sysmail data find"));
}
@Override
public int updateById(SysMailPojo pojo) {
SysMailRecord record = getDslContext().newRecord(SYS_MAIL, pojo);
record.changed(SYS_MAIL.ID, false);
if (pojo.getPassword() == null) {
record.changed(SYS_MAIL.PASSWORD, false);
}
return getDslContext().executeUpdate(record);
}
}

View File

@@ -0,0 +1,38 @@
package com.databasir.dao.impl;
import com.databasir.dao.tables.pojos.TableColumnDocumentPojo;
import com.databasir.dao.tables.records.TableColumnDocumentRecord;
import lombok.Getter;
import org.jooq.DSLContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import java.util.List;
import static com.databasir.dao.Tables.TABLE_COLUMN_DOCUMENT;
@Repository
public class TableColumnDocumentDao extends BaseDao<TableColumnDocumentRecord, TableColumnDocumentPojo> {
@Autowired
@Getter
private DSLContext dslContext;
public TableColumnDocumentDao() {
super(TABLE_COLUMN_DOCUMENT, TableColumnDocumentPojo.class);
}
public List<TableColumnDocumentPojo> selectByDatabaseDocumentId(Integer schemaDocumentId) {
return getDslContext()
.select(TABLE_COLUMN_DOCUMENT.fields()).from(TABLE_COLUMN_DOCUMENT)
.where(TABLE_COLUMN_DOCUMENT.DATABASE_DOCUMENT_ID.eq(schemaDocumentId))
.fetchInto(TableColumnDocumentPojo.class);
}
public void deleteByDatabaseDocumentId(Integer schemaDocumentId) {
getDslContext()
.deleteFrom(TABLE_COLUMN_DOCUMENT).where(TABLE_COLUMN_DOCUMENT.DATABASE_DOCUMENT_ID.eq(schemaDocumentId))
.execute();
}
}

View File

@@ -0,0 +1,36 @@
package com.databasir.dao.impl;
import com.databasir.dao.tables.pojos.TableDocumentPojo;
import com.databasir.dao.tables.records.TableDocumentRecord;
import lombok.Getter;
import org.jooq.DSLContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import java.util.List;
import static com.databasir.dao.Tables.TABLE_DOCUMENT;
@Repository
public class TableDocumentDao extends BaseDao<TableDocumentRecord, TableDocumentPojo> {
@Autowired
@Getter
private DSLContext dslContext;
public TableDocumentDao() {
super(TABLE_DOCUMENT, TableDocumentPojo.class);
}
public List<TableDocumentPojo> selectByDatabaseDocumentId(Integer schemaDocumentId) {
return getDslContext()
.select(TABLE_DOCUMENT.fields()).from(TABLE_DOCUMENT).where(TABLE_DOCUMENT.DATABASE_DOCUMENT_ID.eq(schemaDocumentId))
.fetchInto(TableDocumentPojo.class);
}
public void deleteByDatabaseDocumentId(Integer schemaDocumentId) {
getDslContext()
.deleteFrom(TABLE_DOCUMENT).where(TABLE_DOCUMENT.DATABASE_DOCUMENT_ID.eq(schemaDocumentId))
.execute();
}
}

View File

@@ -0,0 +1,38 @@
package com.databasir.dao.impl;
import com.databasir.dao.tables.pojos.TableIndexDocumentPojo;
import com.databasir.dao.tables.records.TableIndexDocumentRecord;
import lombok.Getter;
import org.jooq.DSLContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import java.util.List;
import static com.databasir.dao.Tables.TABLE_INDEX_DOCUMENT;
@Repository
public class TableIndexDocumentDao extends BaseDao<TableIndexDocumentRecord, TableIndexDocumentPojo> {
@Autowired
@Getter
private DSLContext dslContext;
public TableIndexDocumentDao() {
super(TABLE_INDEX_DOCUMENT, TableIndexDocumentPojo.class);
}
public List<TableIndexDocumentPojo> selectByDatabaseMetaId(Integer schemaMetaId) {
return getDslContext()
.select(TABLE_INDEX_DOCUMENT.fields()).from(TABLE_INDEX_DOCUMENT)
.where(TABLE_INDEX_DOCUMENT.DATABASE_DOCUMENT_ID.eq(schemaMetaId))
.fetchInto(TableIndexDocumentPojo.class);
}
public void deleteByDatabaseMetaId(Integer schemaMetaId) {
getDslContext()
.deleteFrom(TABLE_INDEX_DOCUMENT).where(TABLE_INDEX_DOCUMENT.DATABASE_DOCUMENT_ID.eq(schemaMetaId))
.execute();
}
}

View File

@@ -0,0 +1,36 @@
package com.databasir.dao.impl;
import com.databasir.dao.tables.pojos.TableTriggerDocumentPojo;
import com.databasir.dao.tables.records.TableTriggerDocumentRecord;
import lombok.Getter;
import org.jooq.DSLContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import java.util.List;
import static com.databasir.dao.Tables.TABLE_TRIGGER_DOCUMENT;
@Repository
public class TableTriggerDocumentDao extends BaseDao<TableTriggerDocumentRecord, TableTriggerDocumentPojo> {
@Autowired
@Getter
private DSLContext dslContext;
public TableTriggerDocumentDao() {
super(TABLE_TRIGGER_DOCUMENT, TableTriggerDocumentPojo.class);
}
public List<TableTriggerDocumentPojo> selectByDatabaseDocumentId(Integer schemaDocumentId) {
return getDslContext()
.select(TABLE_TRIGGER_DOCUMENT.fields()).from(TABLE_TRIGGER_DOCUMENT).where(TABLE_TRIGGER_DOCUMENT.DATABASE_DOCUMENT_ID.eq(schemaDocumentId))
.fetchInto(TableTriggerDocumentPojo.class);
}
public void deleteByDatabaseDocumentId(Integer schemaDocumentId) {
getDslContext()
.deleteFrom(TABLE_TRIGGER_DOCUMENT).where(TABLE_TRIGGER_DOCUMENT.DATABASE_DOCUMENT_ID.eq(schemaDocumentId))
.execute();
}
}

View File

@@ -0,0 +1,140 @@
package com.databasir.dao.impl;
import com.databasir.dao.Databasir;
import com.databasir.dao.tables.pojos.UserPojo;
import com.databasir.dao.tables.records.UserRecord;
import com.databasir.dao.value.GroupMemberDetailPojo;
import lombok.Getter;
import org.jooq.Condition;
import org.jooq.DSLContext;
import org.jooq.SortField;
import org.jooq.TableField;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Repository;
import java.util.*;
import static com.databasir.dao.Tables.USER;
import static com.databasir.dao.Tables.USER_ROLE;
@Repository
public class UserDao extends BaseDao<UserRecord, UserPojo> {
@Autowired
@Getter
private DSLContext dslContext;
public UserDao() {
super(USER, UserPojo.class);
}
public void updateEnabledByUserId(Integer userId, Boolean enabled) {
dslContext
.update(USER).set(USER.ENABLED, enabled).where(USER.ID.eq(userId))
.execute();
}
public void updatePassword(Integer userId, String password) {
dslContext
.update(USER).set(USER.PASSWORD, password).where(USER.ID.eq(userId))
.execute();
}
public List<UserPojo> selectUserIdIn(List<Integer> userIds) {
if (userIds == null || userIds.isEmpty()) {
return Collections.emptyList();
}
return dslContext
.select(USER.fields()).from(USER)
.where(USER.ID.in(userIds))
.fetchInto(UserPojo.class);
}
public List<UserPojo> selectLimitUsersByRoleAndGroup(Integer groupId,
String role,
Integer size) {
return dslContext
.select(USER.fields()).from(USER)
.innerJoin(USER_ROLE).on(USER_ROLE.USER_ID.eq(USER.ID))
.where(USER_ROLE.GROUP_ID.eq(groupId).and(USER_ROLE.ROLE.eq(role)))
.orderBy(USER_ROLE.ID.desc())
.limit(size)
.fetchInto(UserPojo.class);
}
public Optional<UserPojo> selectByEmail(String email) {
return dslContext
.select(USER.fields()).from(USER).where(USER.EMAIL.eq(email))
.fetchOptionalInto(UserPojo.class);
}
public Optional<UserPojo> selectByEmailOrUsername(String emailOrUsername) {
return dslContext
.select(USER.fields()).from(USER)
.where(USER.EMAIL.eq(emailOrUsername).or(USER.USERNAME.eq(emailOrUsername)))
.fetchOptionalInto(UserPojo.class);
}
public Page<GroupMemberDetailPojo> selectGroupMembers(Integer groupId, Pageable request, Condition condition) {
// total
Integer count = dslContext
.selectCount()
.from(USER)
.innerJoin(USER_ROLE).on(USER.ID.eq(USER_ROLE.USER_ID))
.where(USER_ROLE.GROUP_ID.eq(groupId).and(condition))
.fetchOne(0, int.class);
// data
List<GroupMemberDetailPojo> content = dslContext
.select(USER.NICKNAME, USER.EMAIL, USER.USERNAME, USER.AVATAR, USER.ENABLED,
USER_ROLE.USER_ID, USER_ROLE.ROLE, USER_ROLE.GROUP_ID, USER_ROLE.CREATE_AT)
.from(USER)
.innerJoin(USER_ROLE).on(USER.ID.eq(USER_ROLE.USER_ID))
.where(USER_ROLE.GROUP_ID.eq(groupId).and(condition))
.orderBy(getSortFields(request.getSort()))
.offset(request.getOffset()).limit(request.getPageSize())
.fetchInto(GroupMemberDetailPojo.class);
return new PageImpl<>(content, request, count);
}
protected Collection<SortField<?>> getSortFields(Sort sortSpecification) {
Collection<SortField<?>> querySortFields = new ArrayList<>();
if (sortSpecification == null) {
return querySortFields;
}
Iterator<Sort.Order> specifiedFields = sortSpecification.iterator();
while (specifiedFields.hasNext()) {
Sort.Order specifiedField = specifiedFields.next();
String[] sortFields = specifiedField.getProperty().split("\\.");
String fieldName;
String tableName;
if (sortFields.length == 1) {
tableName = table().getName();
fieldName = sortFields[0];
} else {
tableName = sortFields[0];
fieldName = sortFields[1];
}
TableField tableField = tableField(tableName, fieldName);
querySortFields.add(sortField(specifiedField, tableField));
}
return querySortFields;
}
private TableField tableField(String tableName, String fieldName) {
return (TableField) Databasir.DATABASIR.getTable(tableName).field(fieldName);
}
private SortField<?> sortField(Sort.Order specifiedField, TableField tableField) {
if (specifiedField.getDirection() == Sort.Direction.ASC) {
return tableField.asc();
} else {
return tableField.desc();
}
}
}

View File

@@ -0,0 +1,97 @@
package com.databasir.dao.impl;
import com.databasir.dao.tables.pojos.UserRolePojo;
import com.databasir.dao.tables.records.UserRoleRecord;
import com.databasir.dao.value.GroupMemberSimplePojo;
import lombok.Getter;
import org.jooq.Condition;
import org.jooq.DSLContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Repository;
import java.util.Collections;
import java.util.List;
import static com.databasir.dao.Tables.USER;
import static com.databasir.dao.Tables.USER_ROLE;
@Repository
public class UserRoleDao extends BaseDao<UserRoleRecord, UserRolePojo> {
@Autowired
@Getter
private DSLContext dslContext;
public UserRoleDao() {
super(USER_ROLE, UserRolePojo.class);
}
public List<GroupMemberSimplePojo> selectOwnerNamesByGroupIdIn(List<Integer> groupIdList) {
if (groupIdList == null || groupIdList.isEmpty()) {
return Collections.emptyList();
}
return dslContext.select(USER.NICKNAME, USER_ROLE.GROUP_ID, USER_ROLE.ROLE).from(USER)
.innerJoin(USER_ROLE).on(USER.ID.eq(USER_ROLE.USER_ID))
.where(USER_ROLE.GROUP_ID.in(groupIdList)).and(USER_ROLE.ROLE.eq("GROUP_OWNER"))
.fetchInto(GroupMemberSimplePojo.class);
}
public void deleteByUserIdAndGroupId(Integer userId, Integer groupId) {
dslContext
.deleteFrom(USER_ROLE).where(USER_ROLE.USER_ID.eq(userId).and(USER_ROLE.GROUP_ID.eq(groupId)))
.execute();
}
public Page<UserRolePojo> selectPageByGroupId(Pageable pageable, Integer groupId) {
return super.selectByPage(pageable, USER_ROLE.GROUP_ID.eq(groupId));
}
public void deleteByRoleAndGroupId(String role, Integer groupId) {
dslContext
.deleteFrom(USER_ROLE).where(USER_ROLE.GROUP_ID.eq(groupId).and(USER_ROLE.ROLE.eq(role)))
.execute();
}
public void deleteByGroupId(Integer groupId) {
dslContext
.deleteFrom(USER_ROLE).where(USER_ROLE.GROUP_ID.eq(groupId))
.execute();
}
public void deleteRole(Integer userId, String role) {
dslContext
.deleteFrom(USER_ROLE).where(USER_ROLE.USER_ID.eq(userId).and(USER_ROLE.ROLE.eq(role)))
.execute();
}
public boolean hasRole(Integer userId, Integer groupId) {
Condition condition = USER_ROLE.USER_ID.eq(userId)
.and(USER_ROLE.GROUP_ID.eq(groupId));
return dslContext.fetchExists(USER_ROLE, condition);
}
public boolean hasRole(Integer userId, String role) {
Condition condition = USER_ROLE.USER_ID.eq(userId)
.and(USER_ROLE.ROLE.eq(role));
return dslContext.fetchExists(USER_ROLE, condition);
}
public boolean hasRole(Integer userId, Integer groupId, String role) {
Condition condition = USER_ROLE.USER_ID.eq(userId)
.and(USER_ROLE.GROUP_ID.eq(groupId))
.and(USER_ROLE.ROLE.eq(role));
return dslContext.fetchExists(USER_ROLE, condition);
}
public List<UserRolePojo> selectByUserIds(List<Integer> userIds) {
if (userIds == null || userIds.isEmpty()) {
return Collections.emptyList();
}
return dslContext
.select(USER_ROLE.fields()).from(USER_ROLE).where(USER_ROLE.USER_ID.in(userIds))
.fetchInto(UserRolePojo.class);
}
}

View File

@@ -0,0 +1,21 @@
package com.databasir.dao.strategy;
import org.jooq.codegen.DefaultGeneratorStrategy;
import org.jooq.meta.Definition;
public class DatabasirPojoNamingStrategy extends DefaultGeneratorStrategy {
@Override
public String getJavaClassName(Definition definition, Mode mode) {
if (mode == Mode.POJO) {
String javaClassName = super.getJavaClassName(definition, mode);
if (javaClassName.endsWith("Pojo")) {
return javaClassName;
} else {
return javaClassName + "Pojo";
}
} else {
return super.getJavaClassName(definition, mode);
}
}
}

View File

@@ -0,0 +1,23 @@
package com.databasir.dao.value;
import lombok.Data;
import java.time.LocalDateTime;
@Data
public class GroupMemberDetailPojo {
private Integer userId;
private String role;
private Integer groupId;
private String username;
private String nickname;
private String email;
private LocalDateTime createAt;
}

View File

@@ -0,0 +1,14 @@
package com.databasir.dao.value;
import lombok.Data;
@Data
public class GroupMemberSimplePojo {
private String nickname;
private Integer groupId;
private String role;
}

View File

@@ -0,0 +1,12 @@
package com.databasir.dao.value;
import lombok.Data;
@Data
public class GroupProjectCountPojo {
private Integer groupId;
private Integer count;
}

View File

@@ -0,0 +1,220 @@
CREATE DATABASE IF NOT EXISTS databasir;
USE databasir;
CREATE TABLE sys_key
(
id INT PRIMARY KEY AUTO_INCREMENT,
rsa_public_key TEXT NOT NULL,
rsa_private_key TEXT NOT NULL,
aes_key TEXT NOT NULL,
update_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
create_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
) CHARSET utf8mb4
COLLATE utf8mb4_unicode_ci;
CREATE TABLE sys_mail
(
id INT PRIMARY KEY AUTO_INCREMENT,
username TEXT NOT NULL,
password TEXT NOT NULL,
smtp_host VARCHAR(512) NOT NULL,
smtp_port INT NOT NULL,
update_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
create_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
) CHARSET utf8mb4
COLLATE utf8mb4_unicode_ci;
CREATE TABLE user
(
id INT PRIMARY KEY AUTO_INCREMENT,
email VARCHAR(512) NOT NULL,
username VARCHAR(128) NOT NULL,
password TEXT NOT NULL,
nickname VARCHAR(255) NOT NULL,
avatar VARCHAR(512) DEFAULT NULL,
enabled BOOLEAN NOT NULL DEFAULT FALSE,
update_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
create_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT UNIQUE uk_email (email),
CONSTRAINT UNIQUE uk_username (username)
) CHARSET utf8mb4
COLLATE utf8mb4_unicode_ci;
CREATE TABLE user_role
(
id INT PRIMARY KEY AUTO_INCREMENT,
user_id INT NOT NULL,
role VARCHAR(128) NOT NULL COMMENT 'SYS_OWNER, GROUP_OWNER, GROUP_MEMBER',
group_id INT DEFAULT NULL,
create_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT UNIQUE uk_user_id_group_id_role (user_id, group_id, role)
) CHARSET utf8mb4
COLLATE utf8mb4_unicode_ci;
CREATE TABLE `group`
(
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(255) NOT NULL,
description VARCHAR(512) NOT NULL,
deleted BOOLEAN NOT NULL DEFAULT FALSE,
update_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
create_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT UNIQUE uk_name (name)
) CHARSET utf8mb4
COLLATE utf8mb4_unicode_ci;
CREATE TABLE `project`
(
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(255) NOT NULL,
description TEXT NOT NULL,
group_id INT NOT NULL,
deleted BOOLEAN NOT NULL DEFAULT FALSE,
create_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT UNIQUE uk_group_id_name (group_id, name)
) CHARSET utf8mb4
COLLATE utf8mb4_unicode_ci;
CREATE TABLE `project_sync_rule`
(
id INT PRIMARY KEY AUTO_INCREMENT,
project_id INT NOT NULL,
ignore_table_name_regex_array JSON NOT NULL,
ignore_column_name_regex_array JSON NOT NULL,
update_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
create_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT UNIQUE uk_project_id (project_id)
) CHARSET utf8mb4
COLLATE utf8mb4_unicode_ci;
CREATE TABLE `data_source`
(
id INT PRIMARY KEY AUTO_INCREMENT,
project_id INT NOT NULL,
database_name VARCHAR(512) NOT NULL,
database_type VARCHAR(255) NOT NULL,
url TEXT NOT NULL,
username TEXT NOT NULL,
password TEXT NOT NULL,
update_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
create_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT UNIQUE uk_project_id (project_id)
) CHARSET utf8mb4
COLLATE utf8mb4_unicode_ci;
CREATE TABLE `data_source_property`
(
id INT PRIMARY KEY AUTO_INCREMENT,
data_source_id INT NOT NULL,
`key` TEXT NOT NULL,
`value` TEXT NOT NULL,
create_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
INDEX idx_data_source_id (data_source_id)
) CHARSET utf8mb4
COLLATE utf8mb4_unicode_ci;
CREATE TABLE database_document
(
id INT PRIMARY KEY AUTO_INCREMENT,
project_id INT NOT NULL,
database_name TEXT NOT NULL,
product_name TEXT NOT NULL,
product_version TEXT NOT NULL,
version BIGINT NOT NULL DEFAULT 1,
update_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
create_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT uk_project_id UNIQUE (project_id)
) CHARSET utf8mb4
COLLATE utf8mb4_unicode_ci;
CREATE TABLE database_document_history
(
id INT PRIMARY KEY AUTO_INCREMENT,
project_id INT NOT NULL,
database_document_id INT NOT NULL,
database_document_object JSON DEFAULT NULL,
version BIGINT NOT NULL,
create_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT uk_connection_id_version UNIQUE (database_document_id, version),
INDEX idx_project_id (project_id)
) CHARSET utf8mb4
COLLATE utf8mb4_unicode_ci;
CREATE TABLE table_document
(
id INT PRIMARY KEY AUTO_INCREMENT,
database_document_id INT NOT NULL,
name TEXT NOT NULL,
type TEXT NOT NULL,
comment TEXT NOT NULL,
create_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
INDEX idx_database_document_id (database_document_id)
) CHARSET utf8mb4
COLLATE utf8mb4_unicode_ci;
CREATE TABLE table_column_document
(
id INT PRIMARY KEY AUTO_INCREMENT,
table_document_id INT NOT NULL,
database_document_id INT NOT NULL,
name TEXT NOT NULL,
type VARCHAR(255) NOT NULL,
comment VARCHAR(512) NOT NULL,
default_value VARCHAR(512) DEFAULT NULL,
size INT NOT NULL,
decimal_digits INT DEFAULT NULL,
nullable VARCHAR(64) NOT NULL COMMENT 'YES, NO, UNKNOWN',
auto_increment VARCHAR(64) NOT NULL COMMENT 'YES, NO, UNKNOWN',
create_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
INDEX idx_table_document_id (table_document_id),
INDEX idx_database_document_id (database_document_id)
) CHARSET utf8mb4
COLLATE utf8mb4_unicode_ci;
CREATE TABLE table_index_document
(
id INT PRIMARY KEY AUTO_INCREMENT,
table_document_id INT NOT NULL,
database_document_id INT NOT NULL,
name TEXT NOT NULL,
is_primary BOOLEAN NOT NULL,
is_unique BOOLEAN NOT NULL,
column_name_array JSON NOT NULL,
create_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
INDEX idx_table_document_id (table_document_id),
INDEX idx_database_document_id (database_document_id)
) CHARSET utf8mb4
COLLATE utf8mb4_unicode_ci;
CREATE TABLE table_trigger_document
(
id INT PRIMARY KEY AUTO_INCREMENT,
table_document_id INT NOT NULL,
database_document_id INT NOT NULL,
timing VARCHAR(64) NOT NULL,
manipulation VARCHAR(128) NOT NULL,
statement TEXT NOT NULL,
trigger_create_at VARCHAR(255) NOT NULL,
create_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
INDEX idx_table_document_id (table_document_id),
INDEX idx_database_document_id (database_document_id)
) CHARSET utf8mb4
COLLATE utf8mb4_unicode_ci;
CREATE TABLE login
(
id INT PRIMARY KEY AUTO_INCREMENT,
user_id INT NOT NULL,
access_token TEXT NOT NULL,
refresh_token TEXT NOT NULL,
access_token_expire_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
refresh_token_expire_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
update_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
create_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT UNIQUE uk_user_id (user_id)
) CHARSET utf8mb4
COLLATE utf8mb4_unicode_ci;