feat: 初始提交

This commit is contained in:
siujamo
2025-12-25 16:08:50 +08:00
commit 8d0b0eb684
153 changed files with 10986 additions and 0 deletions
+149
View File
@@ -0,0 +1,149 @@
### macOS
# General
.DS_Store
.AppleDouble
.LSOverride
# Thumbnails
._*
# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
### Linux
*~
# temporary files which can be created if a process still has a handle open of a deleted file
.fuse_hidden*
# Metadata left by Dolphin file manager, which comes with KDE Plasma
.directory
# Linux trash folder which might appear on any partition or disk
.Trash-*
# .nfs files are created when an open file is removed but is still being accessed
.nfs*
# Log files created by default by the nohup command
nohup.out
### Windows
# Windows thumbnail cache files
Thumbs.db
Thumbs.db:encryptable
ehthumbs.db
ehthumbs_vista.db
# Dump file
*.stackdump
# Folder config file
[Dd]esktop.ini
# Recycle Bin used on file shares
$RECYCLE.BIN/
# Windows Installer files
*.cab
*.msi
*.msix
*.msm
*.msp
# Windows shortcuts
*.lnk
### JetBrains IDE
# Covers JetBrains IDEs: IntelliJ, GoLand, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
# User-specific stuff
.idea
# Gradle and Maven with auto-import
*.iml
*.ipr
# File-based project format
*.iws
# IntelliJ
out/
# JIRA plugin
atlassian-ide-plugin.xml
# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
fabric.properties
# Editor-based HTTP Client
http-client.private.env.json
### Gradle
.gradle
**/build/
!**/src/**/build/
!gradle/wrapper/
!gradle/wrapper/gradle-wrapper.jar
!gradle/wrapper/gradle-wrapper.properties
gradle-app.setting
.gradletasknamecache
# Eclipse Gradle plugin generated files
# Eclipse Core
.project
# JDT-specific (Eclipse Java Development Tools)
.classpath
### Server
# Compiled class file
*.class
# Log file
*.log
# BlueJ files
*.ctxt
# Mobile Tools for Java (J2ME)
.mtj.tmp/
# Package Files #
*.war
*.nar
*.ear
*.zip
*.tar.gz
*.rar
*.jar
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
replay_pid*
# Config
config/*.yml
config/*.yaml
config/*.properties
# Tests
test/
+60
View File
@@ -0,0 +1,60 @@
plugins {
id("java")
id("org.springframework.boot") version "3.5.4"
id("io.spring.dependency-management") version "1.1.7"
}
val artefactVersion: String by project
group = "com.onixbyte.helix"
version = artefactVersion
tasks.withType<JavaCompile> {
options.encoding = "UTF-8"
}
java {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
toolchain {
languageVersion = JavaLanguageVersion.of(17)
}
}
dependencies {
implementation(libs.jspecify.core)
implementation(platform(libs.aws.sdk.bom))
implementation(libs.aws.sdk.s3)
implementation(libs.commons.io)
implementation(libs.commons.lang)
implementation(libs.commons.collections)
implementation(libs.hypersistence.core)
implementation(platform(libs.onixbyte.versionCatalogue))
implementation(libs.onixbyte.tuple)
implementation(libs.onixbyte.commonToolbox)
implementation(libs.onixbyte.identityGenerator)
implementation(libs.onixbyte.captcha)
implementation(libs.onixbyte.regions)
implementation(libs.jwt.core)
implementation(libs.spring.boot.configurationProcessor)
implementation(libs.spring.boot.starter.web)
implementation(libs.spring.boot.starter.webFlux)
implementation(libs.spring.boot.starter.validation)
implementation(libs.spring.boot.starter.redis)
implementation(libs.spring.boot.starter.cache)
implementation(libs.spring.boot.starter.security)
implementation(libs.spring.boot.starter.jpa)
implementation(libs.mybatis.starter.core)
implementation(libs.jackson.jsr310)
testImplementation(libs.spring.boot.starter.test)
testImplementation(libs.reactor.test)
testImplementation(libs.spring.security.test)
testImplementation(libs.mybatis.starter.test)
runtimeOnly(libs.postgres.driver)
testRuntimeOnly(libs.h2.database)
testRuntimeOnly(libs.junit.launcher)
}
tasks.test {
useJUnitPlatform()
}
+84
View File
@@ -0,0 +1,84 @@
spring:
data:
redis:
# Redis 主机
host: ${REDIS_HOST:-localhost}
# Redis 端口
port: ${REDIS_PORT:-6379}
# Redis 数据库索引
database: ${REDIS_DATABASE_INDEX:-0}
# Redis 数据库密码
password: ${REDIS_PASSWORD:-}
datasource:
# 数据库连接池类型
type: com.zaxxer.hikari.HikariDataSource
# 数据库驱动类
driver-class-name: org.postgresql.Driver
# 数据库 URL
url: jdbc:postgresql://${PG_HOST:-localhost}:${PG_PORT:-5432}/${PG_DATABASE:-helix}
# 数据库用户
username: ${PG_USER}
# 数据库密码
password: ${PG_PASSWORD}
app:
asset:
# 是否开启 S3 文件存储服务
enabled: true
# S3 服务端点(若使用非 AWS 提供的 S3 兼容 API,请添加该配置)
# endpoint: https://endpoint.s3.service
# S3 服务区域(详情请见 S3 服务提供商)
# region: apac
# 公开域名
public-host: https://s3.my.app
# 是否开启 Path Style
path-style: false
# 存储桶名称
bucket: dev
# S3 访问密钥 ID
access-key-id: ${S3_ACCESS_KEY_ID}
# S3 访问密钥机密
secret-access-key: ${S3_SECRET_ACCESS_KEY}
# 鉴权配置
authentication:
# Microsoft Entra ID 鉴权配置
msal:
# Microsoft Entra ID 提供的客户端 ID
client-id: ${MSAL_CLIENT_ID}
# Microsoft Entra ID 提供的租户 ID
tenant-id: ${MSAL_TENANT_ID}
# 令牌配置
jwt:
# 令牌签发人
issuer: Helix Server
# 令牌机密
secret: ${TOKEN_SECRET:-1234567890abcdefghijklmnopqrstuv}
# 令牌有效期(Ref java.time.Duration
valid-time: PT2H
# 跨域配置
cors:
# 是否允许身份验证
allow-credentials: true
# 是否允许私有网络
allow-private-network: true
# 允许的请求头列表
allowed-headers: Content-Type
# 允许的请求方法列表(Ref org.springframework.http.HttpMethod
# 2025.11.6注
# 由于 Spring 解析问题,在此处使用小写的情况下会导致在请求头中存在 Origin 时出现 Invalid CORS Request 的问题,请务必使用大写
allowed-methods:
- GET
- POST
- PUT
- PATCH
- DELETE
# 允许的来源域名
allowed-origins: '*'
# 要对外暴露的响应头列表
exposed-headers: X-Authorisation
# 跨域缓存时长
max-age: PT2H
# Captcha 配置
captcha:
# Captcha 长度
length: 6
+324
View File
@@ -0,0 +1,324 @@
/**
* IMPORTANT NOTE ON DATABASE CREATION:
* * If you intend to create the database using a user other than the one specified
* for the application (e.g., 'system_admin' creating a database for 'app_user'),
* please ensure this SQL file is executed by the **corresponding database user**.
* * This is crucial for correctly setting up ownership and default privileges.
* * Example: CREATE DATABASE my_database OWNER app_user;
* * Ensure all necessary user roles and permissions are in place **prior** to
* running this initialisation script.
*/
--- Type Definitions ---
DROP TYPE IF EXISTS USER_STATUS CASCADE;
DROP TYPE IF EXISTS STATUS CASCADE;
DROP TYPE IF EXISTS IDENTITY_PROVIDER CASCADE;
DROP TYPE IF EXISTS SETTING_TYPE CASCADE;
CREATE TYPE USER_STATUS AS ENUM ('ACTIVE', 'INACTIVE', 'LOCKED');
CREATE TYPE STATUS AS ENUM ('ACTIVE', 'INACTIVE');
CREATE TYPE IDENTITY_PROVIDER AS ENUM ('LOCAL', 'OIDC', 'MICROSOFT_ENTRA_ID', 'GOOGLE_OIDC', 'SAML');
CREATE TYPE SETTING_TYPE AS ENUM ('STRING', 'BOOLEAN', 'INT');
--- Departments Table ---
DROP TABLE IF EXISTS departments CASCADE;
CREATE TABLE departments
(
id BIGSERIAL PRIMARY KEY,
name VARCHAR(128) NOT NULL UNIQUE,
parent_id BIGINT NULL REFERENCES departments (id),
sort INT NOT NULL DEFAULT NULL,
status STATUS NOT NULL DEFAULT 'ACTIVE'::STATUS,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX departments_name_index ON departments (name);
CREATE INDEX departments_parent_id_index ON departments (parent_id);
--- Departments Data Insertion ---
INSERT INTO departments (id, name, parent_id, sort, status)
VALUES (1, 'Company HQ', NULL, 1, 'ACTIVE'::STATUS),
(2, 'Human Resources', 1, 1, 'ACTIVE'::STATUS),
(3, 'Finance', 1, 2, 'ACTIVE'::STATUS),
(4, 'Technology', 1, 3, 'ACTIVE'::STATUS),
(5, 'IT Support', 4, 1, 'ACTIVE'::STATUS),
(6, 'Software Development', 4, 2, 'ACTIVE'::STATUS),
(7, 'Operations', 1, 4, 'INACTIVE'::STATUS);
--- Positions Table ---
DROP TABLE IF EXISTS positions CASCADE;
CREATE TABLE positions
(
id BIGSERIAL PRIMARY KEY,
name VARCHAR(128) NOT NULL UNIQUE,
code VARCHAR(64) NULL UNIQUE,
description TEXT,
sort INT NOT NULL DEFAULT 0,
status STATUS NOT NULL DEFAULT 'ACTIVE',
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX positions_name_index ON positions (name);
CREATE INDEX positions_code_index ON positions (code);
CREATE INDEX positions_name_code_index ON positions (name, code);
--- Positions Data Insertion ---
INSERT INTO positions (id, name, code, description, sort, status)
VALUES (1, 'HR Manager', 'HR-MGR',
'Responsible for overseeing recruitment, employee relations, and staff wellbeing.', 1,
'ACTIVE'),
(2, 'Finance Officer', 'FIN-OFC',
'Handles accounts, prepares financial statements, and ensures compliance with regulations.',
2, 'ACTIVE'),
(3, 'IT Support Specialist', 'IT-SPT',
'Provides technical assistance, manages helpdesk queries, and maintains computer systems.',
3, 'ACTIVE'),
(4, 'Software Engineer', 'SWE-ENG',
'Develops and maintains in-house applications, ensuring code quality and system reliability.',
4, 'ACTIVE'),
(5, 'Operations Coordinator', 'OPS-CRD',
'Assists with day-to-day logistics, procurement, and office organisation.', 5, 'INACTIVE');
--- Users Table ---
DROP TABLE IF EXISTS users CASCADE;
CREATE TABLE users
(
id BIGINT PRIMARY KEY,
username VARCHAR(64) UNIQUE NOT NULL,
password VARCHAR(255),
full_name VARCHAR(128) NOT NULL,
email VARCHAR(128) UNIQUE,
region_code VARCHAR(10),
phone_number VARCHAR(32),
avatar_url TEXT,
status USER_STATUS NOT NULL DEFAULT 'ACTIVE',
department_id BIGINT REFERENCES departments (id),
position_id BIGINT REFERENCES positions (id),
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX users_username_index ON users (username);
--- Users Table Indexes ---
CREATE UNIQUE INDEX uidx_users_region_abbreviation_phone_number
ON users (region_abbreviation, phone_number);
--- Users Data Insertion ---
-- NOTE: All phone numbers are generated by ChatGPT, they should not be connected any real person.
INSERT INTO users(id, username, password, full_name, email, region_abbreviation, phone_number, avatar_url,
department_id, position_id, created_at, updated_at)
VALUES (1, 'helix', null, 'Helix Admin', 'admin@helix.onixbyte.dev', 'GB', '7000000000',
'https://gravatar.com/avatar/6ef4c4033f6aa8e43d06bd5e462a6173cc2a960633473721a6f1289cd1b5146f',
1, 1, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP),
(2, 'johndoe', null, 'John Doe', 'johndoe@helix.onixbyte.dev', 'GB', '7000000001',
'https://gravatar.com/avatar/41bcebddd573747d1bd35ef7fae72ebefd6b47f077d42442a2510d35b0c2db92',
6, 4, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP);
--- User Identities Table ---
DROP TABLE IF EXISTS user_identities CASCADE;
CREATE TABLE user_identities
(
user_id BIGINT NOT NULL REFERENCES users (id),
provider IDENTITY_PROVIDER NOT NULL,
external_id VARCHAR(255) NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (user_id, provider, external_id)
);
--- Roles Table ---
DROP TABLE IF EXISTS roles CASCADE;
CREATE TABLE roles
(
id BIGSERIAL PRIMARY KEY,
name VARCHAR(128) NOT NULL UNIQUE,
code VARCHAR(64) NOT NULL UNIQUE,
sort INTEGER NOT NULL,
default_value BOOLEAN NOT NULL DEFAULT FALSE,
description TEXT,
status STATUS NOT NULL DEFAULT 'ACTIVE',
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX roles_name_index ON roles (name);
CREATE INDEX roles_code_index ON roles (code);
CREATE INDEX roles_name_code_index ON roles (name, code);
--- Roles Data Insertion ---
INSERT INTO roles (name, code, sort, default_value, description, status, created_at, updated_at)
VALUES ('Admin', 'admin', 1, FALSE, 'Administrator of this system.', 'ACTIVE'::STATUS,
CURRENT_TIMESTAMP, CURRENT_TIMESTAMP),
('Normal User', 'user', 2, TRUE, 'Normal user of this system.', 'ACTIVE'::STATUS,
CURRENT_TIMESTAMP, CURRENT_TIMESTAMP);
--- User Roles Table ---
DROP TABLE IF EXISTS user_roles CASCADE;
CREATE TABLE user_roles
(
user_id BIGINT NOT NULL
CONSTRAINT user_roles_users_id_fk REFERENCES users,
role_id BIGINT NOT NULL
CONSTRAINT user_roles_roles_id_fk REFERENCES roles,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
CONSTRAINT user_roles_pk PRIMARY KEY (user_id, role_id)
);
--- User Roles Data Insertion ---
INSERT INTO user_roles
VALUES (1, 1),
(2, 2);
--- Authorities Table ---
DROP TABLE IF EXISTS authorities CASCADE;
CREATE TABLE authorities
(
id BIGSERIAL PRIMARY KEY,
code VARCHAR(128) NOT NULL UNIQUE,
name VARCHAR(128) NOT NULL,
description TEXT,
status STATUS NOT NULL DEFAULT 'ACTIVE'::STATUS,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX authorities_code_index ON authorities (code);
--- Authorities Data Insertion ---
INSERT INTO authorities(code, name, description, status, created_at, updated_at)
VALUES ('system:dashboard:read', 'Read Dashboard', 'Read dashboard.', 'ACTIVE'::STATUS,
CURRENT_TIMESTAMP, CURRENT_TIMESTAMP),
('system:user:read', 'Read User', 'Read user.', 'ACTIVE'::STATUS, CURRENT_TIMESTAMP,
CURRENT_TIMESTAMP),
('system:user_detail:read', 'Read User', 'Read user detail.', 'ACTIVE'::STATUS,
CURRENT_TIMESTAMP, CURRENT_TIMESTAMP),
('system:user:write', 'Write User', 'Write user, such as add, edit or delete.',
'ACTIVE'::STATUS, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP),
('system:department:read', 'Read Department', 'Read departments', 'ACTIVE'::STATUS,
CURRENT_TIMESTAMP, CURRENT_TIMESTAMP),
('system:department:write', 'Write Department', 'Write departments.', 'ACTIVE'::STATUS,
CURRENT_TIMESTAMP, CURRENT_TIMESTAMP),
('system:role:read', 'Read Roles', 'Read roles.', 'ACTIVE'::STATUS, CURRENT_TIMESTAMP,
CURRENT_TIMESTAMP),
('system:role:write', 'Write Roles', 'Write roles.', 'ACTIVE'::STATUS,
CURRENT_TIMESTAMP, CURRENT_TIMESTAMP),
('system:authority:read', 'Read Authorities', 'Read authorities.', 'ACTIVE'::STATUS,
CURRENT_TIMESTAMP, CURRENT_TIMESTAMP),
('system:authority:write', 'Write Authorities', 'Write authorities.',
'ACTIVE'::STATUS, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP),
('system:audit_log:read', 'Read Audit Logs', 'Read audit logs.', 'ACTIVE'::STATUS,
CURRENT_TIMESTAMP, CURRENT_TIMESTAMP),
('system:sso:write', 'Manage SSO',
'Manage SSO configurations (such as Microsoft Entra ID, etc.).', 'ACTIVE'::STATUS,
CURRENT_TIMESTAMP, CURRENT_TIMESTAMP),
('system:setting:write', 'Write System Settings', 'Write system settings.',
'ACTIVE'::STATUS, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP);
--- Role Authorities Table ---
DROP TABLE IF EXISTS role_authorities CASCADE;
CREATE TABLE role_authorities
(
role_id BIGINT NOT NULL REFERENCES roles (id),
authority_id BIGINT NOT NULL REFERENCES authorities (id),
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (role_id, authority_id)
);
--- Role Authorities Data Insertion ---
INSERT INTO role_authorities
VALUES (1, 1, CURRENT_TIMESTAMP),
(1, 2, CURRENT_TIMESTAMP),
(1, 3, CURRENT_TIMESTAMP),
(1, 4, CURRENT_TIMESTAMP),
(1, 5, CURRENT_TIMESTAMP),
(1, 6, CURRENT_TIMESTAMP),
(1, 7, CURRENT_TIMESTAMP),
(1, 8, CURRENT_TIMESTAMP),
(1, 9, CURRENT_TIMESTAMP),
(1, 10, CURRENT_TIMESTAMP),
(1, 11, CURRENT_TIMESTAMP),
(1, 12, CURRENT_TIMESTAMP),
(1, 13, CURRENT_TIMESTAMP),
(2, 1, CURRENT_TIMESTAMP),
(2, 2, CURRENT_TIMESTAMP),
(2, 3, CURRENT_TIMESTAMP),
(2, 5, CURRENT_TIMESTAMP),
(2, 7, CURRENT_TIMESTAMP),
(2, 9, CURRENT_TIMESTAMP),
(2, 11, CURRENT_TIMESTAMP);
DROP TABLE IF EXISTS assets;
CREATE TABLE assets
(
id BIGSERIAL NOT NULL PRIMARY KEY,
key VARCHAR(255) NOT NULL UNIQUE,
upload_by BIGINT NOT NULL REFERENCES users (id),
upload_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX assets_key_index ON assets (key);
COMMENT ON TABLE assets IS 'Stores metadata for files or other digital assets within the system.';
COMMENT ON COLUMN assets.id IS 'The unique identifier for the asset, automatically generated by the database.';
COMMENT ON COLUMN assets.key IS 'The unique key or path of the asset within the storage system.';
COMMENT ON COLUMN assets.upload_by IS 'The unique ID of the user who uploaded this asset, referencing the ID in the users table.';
COMMENT ON COLUMN assets.upload_time IS 'The timestamp indicating when the asset was uploaded to the system.';
DROP TABLE IF EXISTS settings;
CREATE TABLE settings
(
id BIGSERIAL NOT NULL PRIMARY KEY,
name VARCHAR(255) NOT NULL,
description VARCHAR(255) NULL,
type SETTING_TYPE NOT NULL,
value VARCHAR(255) NULL,
default_value VARCHAR(255) NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX settings_name_index ON settings (name);
COMMENT ON TABLE settings IS 'Hot-deployable application settings.';
COMMENT ON COLUMN settings.id IS 'Setting unique identifier.';
COMMENT ON COLUMN settings.name IS 'Setting name.';
COMMENT ON COLUMN settings.description IS 'Setting description.';
COMMENT ON COLUMN settings.type IS 'The type of the value.';
COMMENT ON COLUMN settings.value IS 'Setting current value.';
COMMENT ON COLUMN settings.default_value IS 'Setting default value.';
INSERT INTO settings(name, description, type, value, default_value)
VALUES ('captcha-setting::enabled', 'Whether captcha is enabled.', 'BOOLEAN'::SETTING_TYPE, 'true',
'false'),
('auth-setting::register-enabled', 'Whether register is enabled', 'BOOLEAN'::SETTING_TYPE,
'true', 'false');
DROP TABLE IF EXISTS menus;
CREATE TABLE menus
(
id BIGSERIAL NOT NULL PRIMARY KEY,
name VARCHAR(50) NOT NULL,
parent_id BIGINT NULL DEFAULT NULL,
code VARCHAR(255) NOT NULL,
sort INTEGER NOT NULL DEFAULT 0,
path VARCHAR(255) NULL DEFAULT NULL,
is_external_link BOOLEAN NOT NULL DEFAULT FALSE,
is_visible BOOLEAN NOT NULL DEFAULT TRUE,
status STATUS NOT NULL DEFAULT 'ACTIVE'::STATUS,
authority_code VARCHAR(128) NULL DEFAULT NULL,
icon VARCHAR(128) NULL DEFAULT NULL,
create_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
update_time TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE UNIQUE INDEX menus_code_uindex ON menus (code);
INSERT INTO menus(id, name, parent_id, code, sort, path, is_external_link, is_visible, status,
authority_code, icon, create_time, update_time)
VALUES (1, '系统管理', NULL, 'system-manage', 99, NULL, FALSE, TRUE, 'ACTIVE'::STATUS, NULL, NULL,
NOW(), NOW()),
(2, '用户管理', 1, 'user-manage', 1, '/users', FALSE, TRUE, 'ACTIVE'::STATUS,
'system:user:write', NULL, NOW(), NOW());
+1
View File
@@ -0,0 +1 @@
artefactVersion=0.0.1
+55
View File
@@ -0,0 +1,55 @@
[versions]
jspecifyVersion = "1.0.0"
javaJwtVersion = "4.5.0"
postgresDriverVersion = "42.7.7"
h2Version = "2.2.224"
springSecurityVersion = "6.5.2"
springBootVersion = "3.5.4"
reactorVersion = "3.7.8"
junitPlatformVersion = "1.12.2"
onixbyteVersion = "3.2.0"
onixbyteCaptcha = "1.1.0"
onixbyteRegions = "2025.12.0"
awsSdkVersion = "2.25.48"
commonsIoVersion = "2.16.1"
commonsCollections = "4.5.0"
commonsLangVersion = "3.20.0"
mybatisVersion = "3.0.5"
jacksonVersion = "2.19.2"
hypersistenceVersion = "3.14.0"
[libraries]
jwt-core = { group = "com.auth0", name = "java-jwt", version.ref = "javaJwtVersion" }
postgres-driver = { group = "org.postgresql", name = "postgresql", version.ref = "postgresDriverVersion" }
h2-database = { group = "com.h2database", name = "h2", version.ref = "h2Version" }
spring-boot-configurationProcessor = { group = "org.springframework.boot", name = "spring-boot-configuration-processor", version.ref = "springBootVersion" }
spring-boot-starter-web = { group = "org.springframework.boot", name = "spring-boot-starter-web", version.ref = "springBootVersion" }
spring-boot-starter-webFlux = { group = "org.springframework.boot", name = "spring-boot-starter-webflux", version.ref = "springBootVersion" }
spring-boot-starter-validation = { group = "org.springframework.boot", name = "spring-boot-starter-validation", version.ref = "springBootVersion" }
spring-boot-starter-redis = { group = "org.springframework.boot", name = "spring-boot-starter-data-redis", version.ref = "springBootVersion" }
spring-boot-starter-cache = { group = "org.springframework.boot", name = "spring-boot-starter-cache", version.ref = "springBootVersion" }
spring-boot-starter-security = { group = "org.springframework.boot", name = "spring-boot-starter-security", version.ref = "springBootVersion" }
spring-boot-starter-test = { group = "org.springframework.boot", name = "spring-boot-starter-test", version.ref = "springBootVersion" }
spring-security-test = { group = "org.springframework.security", name = "spring-security-test", version.ref = "springSecurityVersion" }
reactor-test = { group = "io.projectreactor", name = "reactor-test", version.ref = "reactorVersion" }
junit-launcher = { group = "org.junit.platform", name = "junit-platform-launcher", version.ref = "junitPlatformVersion" }
onixbyte-versionCatalogue = { group = "com.onixbyte", name = "version-catalogue", version.ref = "onixbyteVersion" }
onixbyte-tuple = { group = "com.onixbyte", name = "tuple", version.ref = "onixbyteVersion" }
onixbyte-commonToolbox = { group = "com.onixbyte", name = "common-toolbox", version.ref = "onixbyteVersion" }
onixbyte-identityGenerator = { group = "com.onixbyte", name = "identity-generator", version.ref = "onixbyteVersion" }
onixbyte-captcha = { group = "com.onixbyte", name = "captcha", version.ref = "onixbyteCaptcha" }
onixbyte-regions = { group = "com.onixbyte", name = "regions4j", version.ref = "onixbyteRegions" }
aws-sdk-bom = { group = "software.amazon.awssdk", name = "bom", version.ref = "awsSdkVersion" }
aws-sdk-s3 = { group = "software.amazon.awssdk", name = "s3" }
commons-io = { group = "commons-io", name = "commons-io", version.ref = "commonsIoVersion" }
commons-collections = { group = "org.apache.commons", name = "commons-collections4", version.ref = "commonsCollections" }
mybatis-starter-core = { group = "org.mybatis.spring.boot", name = "mybatis-spring-boot-starter", version.ref = "mybatisVersion" }
mybatis-starter-test = { group = "org.mybatis.spring.boot", name = "mybatis-spring-boot-starter-test", version.ref = "mybatisVersion" }
jackson-jsr310 = { group = "com.fasterxml.jackson.datatype", name = "jackson-datatype-jsr310", version.ref = "jacksonVersion" }
spring-boot-starter-jpa = { group = "org.springframework.boot", name = "spring-boot-starter-data-jpa" }
hypersistence-core = { group = "io.hypersistence", name = "hypersistence-utils-hibernate-63", version.ref = "hypersistenceVersion" }
commons-lang = { group = "org.apache.commons", name = "commons-lang3", version.ref = "commonsLangVersion" }
jspecify-core = { group = "org.jspecify", name = "jspecify", version.ref = "jspecifyVersion" }
[plugins]
+7
View File
@@ -0,0 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
Vendored
+251
View File
@@ -0,0 +1,251 @@
#!/bin/sh
#
# Copyright © 2015-2021 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# SPDX-License-Identifier: Apache-2.0
#
##############################################################################
#
# Gradle start up script for POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done
# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
warn () {
echo "$*"
} >&2
die () {
echo
echo "$*"
echo
exit 1
} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD=$JAVA_HOME/jre/sh/java
else
JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD=java
if ! command -v java >/dev/null 2>&1
then
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
fi
# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
fi
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped.
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
# treated as '${Hostname}' itself on the command line.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# Stop when "xargs" is not available.
if ! command -v xargs >/dev/null 2>&1
then
die "xargs is not available"
fi
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@"
Vendored
+94
View File
@@ -0,0 +1,94 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@rem SPDX-License-Identifier: Apache-2.0
@rem
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%"=="" set DIRNAME=.
@rem This is normally unused
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute
echo. 1>&2
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo. 1>&2
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
if %ERRORLEVEL% equ 0 goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
+1
View File
@@ -0,0 +1 @@
rootProject.name = "helix-server"
@@ -0,0 +1,31 @@
package com.onixbyte.helix;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;
/**
* Application entrance.
*
* @author zihluwang
* @see SpringBootApplication
* @see EnableCaching
* @see SpringApplication
* @since 1.0.0
*/
@EnableCaching
@SpringBootApplication
public class HelixApplication {
/**
* Main method that serves as the entry point for the Helix application.
*
* @param args command-line arguments passed to the application, which can be used to override
* default configuration properties or specify runtime options
*/
public static void main(String[] args) {
SpringApplication.run(HelixApplication.class, args);
}
}
@@ -0,0 +1,127 @@
package com.onixbyte.helix.client;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import java.time.Duration;
import java.util.Objects;
/**
* A client class for interacting with Redis, providing simple and type-safe access to common Redis
* operations such as setting, retrieving, incrementing, decrementing, and deleting keys.
* <p>
* This class abstracts the direct usage of {@link RedisTemplate} for value operations.
*
* @author zihluwang
*/
@Component
public class RedisClient {
private final RedisTemplate<String, Object> redisTemplate;
/**
* Constructs a new RedisClient with the specified {@link RedisTemplate}.
*
* @param redisTemplate the template used for Redis interaction
*/
@Autowired
public RedisClient(RedisTemplate<String, Object> redisTemplate) {
this.redisTemplate = redisTemplate;
}
/**
* Set a value for a given key in Redis.
*
* @param key the key to set
* @param value the value associated with the key
* @param <T> the type of the value
*/
public <T> void set(String key, T value) {
redisTemplate.opsForValue().set(key, value);
}
/**
* Set a value for a given key in Redis with a specified expiry timeout.
*
* @param key the key to set
* @param value the value associated with the key
* @param timeout the time after which the key should expire
* @param <T> the type of the value
*/
public <T> void set(String key, T value, Duration timeout) {
redisTemplate.opsForValue().set(key, value, timeout);
}
/**
* Get the value associated with a given key from Redis.
* <p>
* The returned object is of type {@code Object} and may require manual casting.
*
* @param key the key to retrieve
* @return the value associated with the key, or {@code null} if the key does not exist
*/
public Object get(String key) {
return redisTemplate.opsForValue().get(key);
}
/**
* Get the value associated with a given key from Redis, attempting to cast it to the
* specified type.
*
* @param key the key to retrieve
* @param type the class type to cast the retrieved value to
* @param <T> the type of the expected value
* @return the value associated with the key, cast to type T, or {@code null} if the key does
* not exist
* @throws IllegalStateException if the retrieved value cannot be cast to the specified type
*/
public <T> T get(String key, Class<T> type) {
var value = redisTemplate.opsForValue().get(key);
if (Objects.isNull(value)) {
return null;
}
if (type.isInstance(value)) {
return type.cast(value);
}
throw new IllegalStateException("Cannot cast " + value.getClass().getName() + " to " + type.getName());
}
/**
* Increment the value of the key by one.
* <p>
* If the key does not exist, it is created and set to 0 before the increment operation. If the
* value stored at the key is not an integer, an exception may be thrown by Redis.
*
* @param key the key to increment
* @return the new value of the key after the increment
*/
public Long increment(String key) {
return redisTemplate.opsForValue().increment(key);
}
/**
* Decrement the value of the key by one.
* <p>
* If the key does not exist, it is created and set to 0 before the decrement operation. If the
* value stored at the key is not an integer, an exception may be thrown by Redis.
*
* @param key the key to decrement
* @return the new value of the key after the decrement
*/
public Long decrement(String key) {
return redisTemplate.opsForValue().decrement(key);
}
/**
* Delete a key from Redis.
*
* @param key the key to delete
* @return {@code true} if the key was deleted, {@code false} if the key did not exist
*/
public boolean delete(String key) {
return redisTemplate.delete(key);
}
}
@@ -0,0 +1,57 @@
package com.onixbyte.helix.client;
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.onixbyte.helix.domain.entity.User;
import com.onixbyte.helix.properties.TokenProperties;
import com.onixbyte.helix.utils.DateTimeUtil;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
/**
* A client class responsible for generating JSON Web Tokens (JWT) for user authentication and
* authorisation purposes.
* <p>
* It uses the {@link com.auth0.jwt.JWT} library to create and sign tokens based on user details and
* configured token properties.
*
* @author zihluwang
*/
@Component
public class TokenClient {
private final Algorithm algorithm;
private final TokenProperties tokenProperties;
/**
* Constructs a new TokenClient with the necessary algorithm and token properties.
*
* @param algorithm the signing algorithm used to secure the JWT
* @param tokenProperties the configuration properties for the token, such as issuer and
* validity period
*/
public TokenClient(Algorithm algorithm, TokenProperties tokenProperties) {
this.algorithm = algorithm;
this.tokenProperties = tokenProperties;
}
/**
* Generate a JSON Web Token to the current user.
*
* @param user the current user for whom the token is being generated
* @return a JWT string
*/
public String generateToken(User user) {
var issuedAt = LocalDateTime.now();
var expiresAt = issuedAt.plus(tokenProperties.validTime());
return JWT.create()
.withSubject(user.getUsername())
.withAudience("Helix Web")
.withIssuer(tokenProperties.issuer())
.withIssuedAt(DateTimeUtil.asInstant(issuedAt))
.withExpiresAt(DateTimeUtil.asInstant(expiresAt))
.sign(algorithm);
}
}
@@ -0,0 +1,45 @@
package com.onixbyte.helix.common.datetime;
import java.time.format.DateTimeFormatter;
/**
* Utility class providing predefined {@link DateTimeFormatter} instances for common date and
* time patterns. These formatters can be used to parse and format {@code java.time} objects
* consistently throughout an application.
*
* @author zihluwang
*/
public class DateTimeFormatters {
/**
* A {@link DateTimeFormatter} for formatting and parsing full date and time with seconds, using
* the pattern "yyyy-MM-dd HH:mm:ss".
* <p>
* Example: "{@code 2023-10-27 15:30:45}"
*/
public static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
/**
* A {@link DateTimeFormatter} for formatting and parsing dates only, using the
* pattern "yyyy-MM-dd".
* <p>
* Example: "{@code 2023-10-27}"
*/
public static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");
/**
* A {@link DateTimeFormatter} for formatting and parsing times only, using the
* pattern "HH:mm:ss".
* <p>
* Example: "{@code 15:30:45}"
*/
public static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern("HH:mm:ss");
/**
* A {@link DateTimeFormatter} for formatting and parsing year and month only, using the
* pattern "yyyy-MM".
* <p>
* Example: "{@code 2023-10}"
*/
public static final DateTimeFormatter YEAR_MONTH_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM");
}
@@ -0,0 +1,34 @@
package com.onixbyte.helix.common.jackson;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer;
import com.onixbyte.helix.common.datetime.DateTimeFormatters;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
public class JacksonModules {
public static final SimpleModule DATE_TIME_MODULE = initialiseDateTimeModule();
private static SimpleModule initialiseDateTimeModule() {
var javaTimeModule = new JavaTimeModule();
javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatters.DATE_TIME_FORMATTER));
javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatters.DATE_TIME_FORMATTER));
javaTimeModule.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatters.DATE_FORMATTER));
javaTimeModule.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatters.DATE_FORMATTER));
javaTimeModule.addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatters.TIME_FORMATTER));
javaTimeModule.addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatters.TIME_FORMATTER));
return javaTimeModule;
}
}
@@ -0,0 +1,12 @@
package com.onixbyte.helix.common.regex;
import java.util.regex.Pattern;
public class Patterns {
public static final Pattern EMAIL = Pattern.compile("^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$");
public static final Pattern IMAGE_URL = Pattern.compile("^https?://.*\\.(?:png|jpg|jpeg|gif|webp|svg|avif)(?:\\?.*)?$", Pattern.CASE_INSENSITIVE);
public static final Pattern GRAVATAR_IMAGE_URL = Pattern.compile("^https?://(?:[a-z0-9-]+\\.)?gravatar\\.com/avatar/([a-f0-9]{32})(?:\\?.*)?$", Pattern.CASE_INSENSITIVE);
}
@@ -0,0 +1,67 @@
package com.onixbyte.helix.config;
import com.onixbyte.helix.properties.AssetProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.s3.S3Client;
import software.amazon.awssdk.services.s3.S3Configuration;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Optional;
/**
* Configuration class for asset storage services.
* <p>
* Enables configuration properties for S3 file storage services. Individual service beans are
* created by their respective service classes to better support conditional configuration.
*
* @author zihluwang
* @since 1.0.0
*/
@Configuration
@EnableConfigurationProperties({AssetProperties.class})
public class AssetConfig {
/**
* S3Client to store assets into S3 service.
*
* @param assetProperties asset properties
* @return an S3 Client reference to custom S3 configuration properties
*/
@Bean
public S3Client s3Client(AssetProperties assetProperties) {
// initialise AWS credentials
var credentials = AwsBasicCredentials.create(
assetProperties.accessKeyId(),
assetProperties.secretAccessKey()
);
// prepare s3 client
var s3ClientBuilder = S3Client.builder()
.region(Region.of(assetProperties.region()))
.credentialsProvider(StaticCredentialsProvider.create(credentials));
// override endpoint
Optional.ofNullable(assetProperties.endpoint())
.ifPresent((endpoint) -> {
try {
s3ClientBuilder.endpointOverride(new URI(endpoint));
} catch (URISyntaxException e) {
throw new IllegalArgumentException("Endpoint is not valid.");
}
});
// set path style
s3ClientBuilder.serviceConfiguration(S3Configuration.builder()
.pathStyleAccessEnabled(assetProperties.pathStyle())
.build()
);
return s3ClientBuilder.build();
}
}
@@ -0,0 +1,28 @@
package com.onixbyte.helix.config;
import com.onixbyte.helix.properties.ApplicationProperties;
import com.onixbyte.helix.properties.MsalProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;
/**
* Configuration class for authentication-related components and properties.
* <p>
* This configuration class is responsible for enabling and managing custom configuration properties
* related to authentication mechanisms within the Helix application. It specifically enables the
* {@link MsalProperties} configuration properties to support Microsoft Authentication Library
* (MSAL) integration.
* <p>
* The class serves as a central point for authentication configuration, ensuring that all
* authentication-related properties are properly loaded and made available to the Spring
* application context.
*
* @author zihluwang
* @since 1.0.0
* @see MsalProperties
* @see EnableConfigurationProperties
*/
@Configuration
@EnableConfigurationProperties({MsalProperties.class, ApplicationProperties.class})
public class AuthenticationConfig {
}
@@ -0,0 +1,99 @@
package com.onixbyte.helix.config;
import com.onixbyte.helix.extension.redis.serializer.JacksonSerialiser;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.RedisSerializer;
import java.time.Duration;
/**
* Configuration class for Redis-based caching components.
* <p>
* This configuration class provides beans for Redis cache management and template operations
* within the Helix application. It configures custom serialisation strategies using
* {@link GenericJackson2JsonRedisSerializer} for values and string serialisation for keys,
* ensuring optimal performance and compatibility with JSON-based data structures.
* <p>
* The configuration includes:
* <ul>
* <li>Custom {@link RedisCacheManager} with JSON serialisation support</li>
* <li>Configured {@link RedisTemplate} for direct Redis operations</li>
* </ul>
*
* @author zihluwang
* @see RedisCacheManager
* @see RedisTemplate
* @see GenericJackson2JsonRedisSerializer
* @since 1.0.0
*/
@Configuration
public class CacheConfig {
/**
* Creates a custom Redis cache manager with JSON serialisation support.
* <p>
* This method configures a {@link RedisCacheManager} that uses string serialisation for cache
* keys and {@link GenericJackson2JsonRedisSerializer} for cache values. This setup ensures that
* complex objects can be stored and retrieved from Redis cache whilst maintaining readability
* and compatibility with JSON-based systems.
*
* @param connectionFactory the Redis connection factory used to establish connections
* @return a configured {@link RedisCacheManager} with custom serialisation settings
* @see RedisCacheManager
* @see GenericJackson2JsonRedisSerializer
* @see RedisSerializationContext
*/
@Bean
public RedisCacheManager cacheManager(
RedisConnectionFactory connectionFactory
) {
var _keySerializer = RedisSerializer.string();
var cacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
.serializeKeysWith(RedisSerializationContext.SerializationPair
.fromSerializer(_keySerializer))
.serializeValuesWith(RedisSerializationContext.SerializationPair
.fromSerializer(JacksonSerialiser.INSTANCE))
.entryTtl(Duration.ofMinutes(90L));
return RedisCacheManager.RedisCacheManagerBuilder
.fromConnectionFactory(connectionFactory)
.cacheDefaults(cacheConfiguration)
.build();
}
/**
* Creates a Redis template for direct Redis operations with custom serialisation.
* <p>
* This method configures a {@link RedisTemplate} that uses string serialisation for keys
* and {@link GenericJackson2JsonRedisSerializer} for values. This template provides low-level
* access to Redis operations whilst ensuring consistent serialisation strategies across
* the application.
* <p>
* The template is fully configured and ready for use after bean creation.
*
* @param connectionFactory the Redis connection factory used to establish connections
* @return a fully configured {@link RedisTemplate} for Redis operations
* @see RedisTemplate
* @see GenericJackson2JsonRedisSerializer
* @see RedisSerializer
*/
@Bean
public RedisTemplate<String, Object> redisTemplate(
RedisConnectionFactory connectionFactory
) {
var redisTemplate = new RedisTemplate<String, Object>();
redisTemplate.setConnectionFactory(connectionFactory);
redisTemplate.setKeySerializer(RedisSerializer.string());
redisTemplate.setValueSerializer(JacksonSerialiser.INSTANCE);
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
}
@@ -0,0 +1,24 @@
package com.onixbyte.helix.config;
import com.onixbyte.captcha.Producer;
import com.onixbyte.captcha.impl.DefaultCaptchaProducer;
import com.onixbyte.captcha.text.impl.DefaultTextProducer;
import com.onixbyte.helix.properties.CaptchaProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@EnableConfigurationProperties({CaptchaProperties.class})
public class CaptchaConfig {
@Bean
public Producer producer(CaptchaProperties captchaProperties) {
var textProducer = DefaultTextProducer.builder()
.length(captchaProperties.length())
.build();
return DefaultCaptchaProducer.builder()
.textProducer(textProducer)
.build();
}
}
@@ -0,0 +1,53 @@
package com.onixbyte.helix.config;
import com.onixbyte.identitygenerator.IdentityGenerator;
import com.onixbyte.identitygenerator.impl.SequentialUuidGenerator;
import com.onixbyte.identitygenerator.impl.SnowflakeIdentityGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* Configuration class for GUID (Globally Unique Identifier) generation components.
* <p>
* This configuration class provides beans for generating unique identifiers throughout the
* Helix application. It utilises the Snowflake algorithm implementation to ensure globally unique,
* time-ordered identifiers that are suitable for distributed systems.
* <p>
* The Snowflake algorithm generates 64-bit identifiers composed of:
* <ul>
* <li>Timestamp (41 bits) - milliseconds since epoch</li>
* <li>Machine ID (10 bits) - identifies the generating machine</li>
* <li>Sequence number (12 bits) - counter for same millisecond</li>
* </ul>
*
* @author zihluwang
* @since 1.0.0
* @see IdentityGenerator
* @see SnowflakeIdentityGenerator
*/
@Configuration
public class GuidConfig {
/**
* Creates a Snowflake-based identity generator for user IDs.
* <p>
* This method configures a {@link SnowflakeIdentityGenerator} with machine ID and data centre
* ID both set to 0. The generator produces unique 64-bit Long identifiers suitable for user
* entity primary keys in distributed environments.
* <p>
* The generated IDs are:
* <ul>
* <li>Globally unique across all instances</li>
* <li>Time-ordered (newer IDs have higher values)</li>
* <li>Highly performant with minimal coordination overhead</li>
* </ul>
*
* @return a configured {@link SnowflakeIdentityGenerator} instance for generating user IDs
* @see SnowflakeIdentityGenerator
* @see IdentityGenerator
*/
@Bean
public IdentityGenerator<Long> userIdentityGenerator() {
return new SnowflakeIdentityGenerator(0x0, 0x0);
}
}
@@ -0,0 +1,20 @@
package com.onixbyte.helix.config;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.onixbyte.helix.common.jackson.JacksonModules;
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class JacksonConfig {
@Bean
public Jackson2ObjectMapperBuilderCustomizer jacksonCustomiser() {
return (builder) -> {
builder.modules(JacksonModules.DATE_TIME_MODULE);
builder.featuresToDisable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
};
}
}
@@ -0,0 +1,9 @@
package com.onixbyte.helix.config;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@MapperScan(basePackages = {"com.onixbyte.helix.mapper"})
public class MyBatisConfig {
}
@@ -0,0 +1,209 @@
package com.onixbyte.helix.config;
import com.auth0.jwt.algorithms.Algorithm;
import com.onixbyte.helix.filter.TokenAuthenticationFilter;
import com.onixbyte.helix.properties.CorsProperties;
import com.onixbyte.helix.properties.TokenProperties;
import com.onixbyte.helix.security.entrypoint.UnauthorisedAuthenticationEntryPoint;
import com.onixbyte.helix.security.provider.UsernamePasswordAuthenticationProvider;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
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.SecurityFilterChain;
import org.springframework.security.web.access.ExceptionTranslationFilter;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import java.util.List;
import java.util.stream.Stream;
/**
* Configuration class for Spring Security components and policies.
* <p>
* This configuration class establishes comprehensive security settings for the Helix application,
* including CORS policies, authentication mechanisms, password encoding, and JWT token handling.
* It configures a stateless security architecture suitable for modern web applications and APIs.
* <p>
* Key security features configured:
* <ul>
* <li>CORS (Cross-Origin Resource Sharing) configuration</li>
* <li>Stateless session management</li>
* <li>JWT-based authentication with HMAC256 algorithm</li>
* <li>BCrypt password encoding</li>
* <li>Method-level security annotations</li>
* <li>Custom authentication providers</li>
* </ul>
*
* @author zihluwang
* @see EnableWebSecurity
* @see EnableMethodSecurity
* @see TokenProperties
* @see CorsProperties
* @since 1.0.0
*/
@Configuration
@EnableWebSecurity
@EnableMethodSecurity
@EnableConfigurationProperties({TokenProperties.class, CorsProperties.class})
public class SecurityConfig {
/**
* Creates a CORS configuration source based on application properties.
* <p>
* This method configures Cross-Origin Resource Sharing (CORS) policies using the
* {@link CorsProperties} configuration. It sets up allowed origins, headers, methods,
* credentials handling, and other CORS-related settings to enable secure cross-origin requests
* from web browsers.
* <p>
* The configuration is applied globally to all endpoints (/**) within the application.
*
* @param properties the CORS configuration properties containing allowed origins, headers,
* methods, etc
* @return a configured {@link CorsConfigurationSource} for handling cross-origin requests
* @see CorsProperties
* @see CorsConfiguration
* @see UrlBasedCorsConfigurationSource
*/
@Bean
public CorsConfigurationSource corsConfigurationSource(
CorsProperties properties
) {
var corsConfiguration = new CorsConfiguration();
corsConfiguration.setAllowCredentials(properties.allowCredentials());
corsConfiguration.setAllowedOrigins(List.of(properties.allowedOrigins()));
corsConfiguration.setAllowedHeaders(List.of(properties.allowedHeaders()));
corsConfiguration.setAllowedMethods(Stream.of(properties.allowedMethods())
.map(HttpMethod::name)
.toList());
corsConfiguration.setMaxAge(properties.maxAge());
corsConfiguration.setAllowPrivateNetwork(properties.allowPrivateNetwork());
corsConfiguration.setExposedHeaders(List.of(properties.exposedHeaders()));
var corsConfigurationSource = new UrlBasedCorsConfigurationSource();
corsConfigurationSource.registerCorsConfiguration("/**", corsConfiguration);
return corsConfigurationSource;
}
/**
* Configures the Spring Security filter chain for HTTP requests.
* <p>
* This method establishes the core security policies for the application, including:
* <ul>
* <li>CORS configuration integration</li>
* <li>CSRF protection disabled (suitable for stateless APIs)</li>
* <li>Stateless session management</li>
* <li>Request authorization rules with public and protected endpoints</li>
* </ul>
* <p>
* The configuration permits access to error pages and authentication endpoints whilst requiring
* authentication for all other requests. Logout endpoints require authentication to prevent
* unauthorised session termination.
*
* @param httpSecurity the HTTP security configuration builder
* @param corsConfigurationSource the CORS configuration source for cross-origin requests
* @return a configured {@link SecurityFilterChain} for processing HTTP requests
* @throws Exception if any exception occurs during security filter chain construction
* @see HttpSecurity
* @see SecurityFilterChain
* @see SessionCreationPolicy#STATELESS
*/
@Bean
public SecurityFilterChain securityFilterChain(
HttpSecurity httpSecurity,
CorsConfigurationSource corsConfigurationSource,
TokenAuthenticationFilter tokenAuthenticationFilter,
UnauthorisedAuthenticationEntryPoint unauthorisedAuthenticationEntryPoint
) throws Exception {
return httpSecurity
.cors((cors) -> cors
.configurationSource(corsConfigurationSource))
.csrf(AbstractHttpConfigurer::disable)
.sessionManagement((customiser) -> customiser
.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authorizeHttpRequests((customiser) -> customiser
.requestMatchers("/error", "/error/**").permitAll()
.requestMatchers("/captcha", "/captcha/**").permitAll()
.requestMatchers("/auth/**").permitAll()
.requestMatchers("/auth/logout").authenticated()
.anyRequest().authenticated()
)
.exceptionHandling((exceptionHandling) -> exceptionHandling
.authenticationEntryPoint(unauthorisedAuthenticationEntryPoint))
.addFilterAfter(tokenAuthenticationFilter, ExceptionTranslationFilter.class)
.build();
}
/**
* Creates a password encoder for secure password hashing.
* <p>
* This method provides a {@link BCryptPasswordEncoder} instance that uses the BCrypt hashing
* algorithm to securely encode passwords. BCrypt is a adaptive hash function designed for
* password hashing that includes a salt to protect against rainbow table attacks and is
* computationally expensive to resist brute-force attacks.
* <p>
* The encoder is used throughout the application for password verification during
* authentication and for encoding new passwords during user registration.
*
* @return a {@link BCryptPasswordEncoder} instance for secure password operations
* @see BCryptPasswordEncoder
* @see PasswordEncoder
*/
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
/**
* Creates an authentication manager with custom authentication providers.
* <p>
* This method configures a {@link ProviderManager} that coordinates multiple authentication
* providers to handle different authentication mechanisms within the application. The manager
* attempts authentication using each configured provider until one succeeds or all fail.
* <p>
* Currently configured for extensibility to support various authentication providers such as
* Microsoft Entra ID, local database authentication, or other identity providers as needed.
*
* @return a {@link ProviderManager} instance configured with authentication providers
* @see ProviderManager
* @see AuthenticationManager
*/
@Bean
public AuthenticationManager authenticationManager(
UsernamePasswordAuthenticationProvider usernamePasswordAuthenticationProvider
) {
return new ProviderManager(
usernamePasswordAuthenticationProvider
);
}
/**
* Creates the JWT signing algorithm using application token properties.
* <p>
* This method configures an HMAC256 algorithm instance using the secret key specified in
* the {@link TokenProperties}. The algorithm is used for signing and verifying JWT tokens
* throughout the application, ensuring token integrity and authenticity.
* <p>
* HMAC256 provides a good balance of security and performance for JWT token signing in
* most applications.
*
* @param properties the token configuration properties containing the signing secret
* @return a configured {@link Algorithm} instance for JWT token operations
* @see Algorithm
* @see TokenProperties
*/
@Bean
public Algorithm algorithm(TokenProperties properties) {
return Algorithm.HMAC256(properties.secret());
}
}
@@ -0,0 +1,9 @@
package com.onixbyte.helix.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
@Configuration
@EnableJpaRepositories(basePackages = {"com.onixbyte.helix.repository"})
public class SpringDataConfig {
}
@@ -0,0 +1,57 @@
package com.onixbyte.helix.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.reactive.function.client.WebClient;
/**
* Configuration class for Spring WebFlux reactive web components.
* <p>
* This configuration class provides beans for reactive web programming components within the Helix
* application. It configures WebClient instances and other reactive web-related components that
* enable non-blocking, asynchronous HTTP communication with external services.
* <p>
* The configuration supports:
* <ul>
* <li>Reactive HTTP client configuration</li>
* <li>Non-blocking I/O operations</li>
* <li>Asynchronous request/response handling</li>
* </ul>
*
* @author zihluwang
* @since 1.0.0
* @see WebClient
* @see Configuration
*/
@Configuration
public class WebFluxConfig {
/**
* Creates a reactive WebClient for HTTP communication with external services.
* <p>
* This method configures a {@link WebClient} instance that provides a modern, reactive approach
* to HTTP client operations. The WebClient supports non-blocking I/O and is built on top of
* Reactor Netty, making it suitable for high-performance, scalable applications.
* <p>
* The client is configured with default settings and can be used throughout the application for
* making HTTP requests to external APIs, microservices, or other web resources in a reactive,
* non-blocking manner.
* <p>
* Key features:
* <ul>
* <li>Non-blocking I/O operations</li>
* <li>Reactive streams support</li>
* <li>Built-in support for JSON serialisation/deserialisation</li>
* <li>Configurable timeouts and retry mechanisms</li>
* </ul>
*
* @return a configured {@link WebClient} instance for reactive HTTP operations
* @see WebClient
* @see WebClient.Builder
*/
@Bean
public WebClient webClient() {
return WebClient.builder()
.build();
}
}
@@ -0,0 +1,10 @@
package com.onixbyte.helix.constant;
public class AssetPrefix {
public static final String UPLOADS = "uploads";
public static final String AVATARS = "avatars";
public static final String PROFILES = "profiles";
}
@@ -0,0 +1,18 @@
package com.onixbyte.helix.constant;
public class CacheName {
public static final String USER = "user";
public static final String AUTHORITIES_OF_USER = "user-authorities";
public static final String ASSET = "asset";
public static final String SETTING = "setting";
public static final String CAPTCHA = "captcha";
public static final String CAPTCHA_SETTING = "captcha-setting";
public static final String AUTH_SETTING = "auth-setting";
}
@@ -0,0 +1,26 @@
package com.onixbyte.helix.constant;
/**
* Constants for external host configurations and endpoints.
* <p>
* This utility class provides centralised definitions for external service hosts, API endpoints,
* and third-party integration points used throughout the Helix application. It serves as a single
* source of truth for external service configurations, promoting maintainability and consistency.
* <p>
* The class is designed to hold static final constants representing:
* <ul>
* <li>External API base URLs</li>
* <li>Third-party service endpoints</li>
* <li>Integration service hosts</li>
* <li>External resource locations</li>
* </ul>
* <p>
* This class cannot be instantiated as it serves purely as a constant container.
*
* @author zihluwang
* @since 1.0.0
*/
public final class ExternalHost {
}
@@ -0,0 +1,23 @@
package com.onixbyte.helix.constant;
public interface FileType {
String getExtension();
enum Image implements FileType {
JPEG("jpeg"),
PNG("png")
;
private final String extension;
Image(String extension) {
this.extension = extension;
}
@Override
public String getExtension() {
return extension;
}
}
}
@@ -0,0 +1,40 @@
package com.onixbyte.helix.constant;
import com.onixbyte.helix.config.AuthenticationConfig;
/**
* Enumeration of supported identity providers for user authentication.
* <p>
* This enumeration defines the various identity providers that the Helix application supports for
* user authentication and authorisation. Each provider represents a different authentication
* mechanism or external identity service that can be used to verify user credentials and establish
* user sessions.
* <p>
* The application supports both local authentication (using internal user database) and external
* identity providers (such as Microsoft Entra ID) to provide flexible authentication options for
* different deployment scenarios and organisational requirements.
*
* @author zihluwang
* @since 1.0.0
* @see AuthenticationConfig
*/
public enum IdentityProvider {
/**
* Local identity provider using the application's internal user database.
* <p>
* This provider authenticates users against locally stored credentials, typically using
* username/email and password combinations. User accounts are managed entirely within the Helix
* application's database.
*/
LOCAL,
/**
* Microsoft Entra ID (formerly Azure Active Directory) identity provider.
* <p>
* This provider enables authentication through Microsoft's cloud-based identity and access
* management service. Users authenticate using their organisational Microsoft accounts,
* supporting features such as single sign-on (SSO) and multi-factor authentication (MFA).
*/
MICROSOFT_ENTRA_ID
}
@@ -0,0 +1,8 @@
package com.onixbyte.helix.constant;
public class SettingName {
public static final String CAPTCHA_ENABLED = "captcha-enabled";
public static final String REGISTER_ENABLED = "register-enabled";
}
@@ -0,0 +1,9 @@
package com.onixbyte.helix.constant;
public enum SettingType {
BOOLEAN,
STRING,
INT,
;
}
@@ -0,0 +1,43 @@
package com.onixbyte.helix.constant;
/**
* Enumeration representing general status states for system entities.
* <p>
* This enumeration provides a standardised way to represent the operational status of various
* system entities, resources, or components within the Helix application. It offers a binary state
* model that can be applied across different domain objects to indicate their current
* operational condition.
* <p>
* The status values are designed to be generic and reusable across multiple contexts, such as
* system configurations, feature toggles, service states, or any other binary operational
* indicators within the application.
*
* @author zihluwang
* @since 1.0.0
*/
public enum Status {
/**
* Indicates that the entity is currently active and operational.
* <p>
* When an entity has an ACTIVE status, it means the entity is:
* <ul>
* <li>Currently enabled and functioning</li>
* <li>Available for use by the system or users</li>
* <li>Participating in normal application operations</li>
* </ul>
*/
ACTIVE,
/**
* Indicates that the entity is currently inactive or disabled.
* <p>
* When an entity has an INACTIVE status, it means the entity is:
* <ul>
* <li>Currently disabled or not functioning</li>
* <li>Temporarily or permanently unavailable</li>
* <li>Excluded from normal application operations</li>
* </ul>
*/
INACTIVE
}
@@ -0,0 +1,60 @@
package com.onixbyte.helix.constant;
/**
* Enumeration representing the various states of user accounts within the system.
* <p>
* This enumeration defines the possible status values for user accounts in the Helix application,
* providing a standardised way to manage user account lifecycle and access control. Each status
* represents a different level of account accessibility and operational capability within
* the system.
* <p>
* User status directly affects authentication, authorisation, and system access permissions.
* The status is typically managed through administrative functions and security policies to ensure
* proper access control and account management.
*
* @author zihluwang
* @since 1.0.0
* @see com.onixbyte.helix.config.SecurityConfiguration
*/
public enum UserStatus {
/**
* Indicates that the user account is active and fully operational.
* <p>
* An ACTIVE user account has:
* <ul>
* <li>Full access to system features and resources</li>
* <li>Ability to authenticate and establish sessions</li>
* <li>Normal operational privileges as per assigned roles</li>
* <li>No restrictions on account usage</li>
* </ul>
*/
ACTIVE,
/**
* Indicates that the user account is inactive or disabled.
* <p>
* An INACTIVE user account:
* <ul>
* <li>Cannot authenticate or access the system</li>
* <li>Is temporarily or permanently disabled</li>
* <li>Retains user data but blocks all system access</li>
* <li>May be reactivated by administrators if appropriate</li>
* </ul>
*/
INACTIVE,
/**
* Indicates that the user account is locked due to security concerns.
* <p>
* A LOCKED user account:
* <ul>
* <li>Is temporarily blocked from system access</li>
* <li>May result from failed authentication attempts</li>
* <li>Could be locked due to security policy violations</li>
* <li>Requires administrative intervention to unlock</li>
* <li>Maintains user data whilst preventing access</li>
* </ul>
*/
LOCKED
}
@@ -0,0 +1,81 @@
package com.onixbyte.helix.controller;
import com.onixbyte.helix.constant.AssetPrefix;
import com.onixbyte.helix.domain.web.response.FileUploadResponse;
import com.onixbyte.helix.exception.BizException;
import com.onixbyte.helix.service.AssetService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
/**
* REST controller for file storage operations. Provides endpoints for uploading, downloading, and
* deleting assets using the configured storage service.
*
* @author zihluwang
* @since 1.0.0
*/
@RestController
@RequestMapping("/assets")
public class AssetController {
private static final Logger log = LoggerFactory.getLogger(AssetController.class);
private final AssetService assetService;
/**
* Constructs a new FileController with the specified file service.
*
* @param assetService the file service to use for file operations
*/
public AssetController(AssetService assetService) {
this.assetService = assetService;
}
/**
* Uploads a file to the configured storage service.
*
* @param file the multipart file to upload
* @return ResponseEntity containing the file URL and metadata, or error message
*/
@PostMapping
public ResponseEntity<FileUploadResponse> uploadFile(
@RequestParam MultipartFile file
) {
try {
if (file.isEmpty()) {
throw new BizException(HttpStatus.BAD_REQUEST, "File cannot be empty.");
}
var fileUrl = assetService.uploadFile(AssetPrefix.UPLOADS, file);
return ResponseEntity.ok()
.header("Location", fileUrl)
.body(new FileUploadResponse(
file.getOriginalFilename(),
file.getContentType(),
file.getSize(),
fileUrl
));
} catch (Exception e) {
log.error("File upload failed: {}", e.getMessage(), e);
throw new BizException(HttpStatus.INTERNAL_SERVER_ERROR,
"Failed upload file: " + e.getMessage());
}
}
/**
* Delete an asset by asset ID.
*
* @param assetId asset ID
*/
@DeleteMapping("/{id:\\d+}")
public void deleteFile(
@PathVariable("id") Long assetId
) {
assetService.deleteAsset(assetId);
}
}
@@ -0,0 +1,39 @@
package com.onixbyte.helix.controller;
import com.onixbyte.helix.domain.web.request.UsernamePasswordLoginRequest;
import com.onixbyte.helix.domain.web.response.LoginSuccessResponse;
import com.onixbyte.helix.service.AuthService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/auth")
public class AuthController {
private static final Logger log = LoggerFactory.getLogger(AuthController.class);
private final AuthService authService;
public AuthController(AuthService authService) {
this.authService = authService;
}
/**
* Perform login with username and password.
*
* @param request login request
* @return detailed user info and authentication token
*/
@PostMapping("/login")
public LoginSuccessResponse loginWithUsernameAndPassword(
@Validated @RequestBody UsernamePasswordLoginRequest request
) {
return authService.login(request);
}
@GetMapping("/register-enabled")
public boolean getRegisterEnabled() {
return authService.getRegisterEnabled();
}
}
@@ -0,0 +1,29 @@
package com.onixbyte.helix.controller;
import com.onixbyte.helix.domain.web.response.CaptchaResponse;
import com.onixbyte.helix.service.CaptchaService;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Optional;
@RestController
@RequestMapping("/captcha")
public class CaptchaController {
private final CaptchaService captchaService;
public CaptchaController(CaptchaService captchaService) {
this.captchaService = captchaService;
}
@GetMapping
public ResponseEntity<CaptchaResponse> getCaptcha() {
var captchaTuple = captchaService.buildCaptcha();
return Optional.ofNullable(captchaTuple)
.map(ResponseEntity::ok)
.orElseGet(() -> ResponseEntity.noContent().build());
}
}
@@ -0,0 +1,31 @@
package com.onixbyte.helix.controller;
import com.onixbyte.helix.domain.entity.Department;
import com.onixbyte.helix.domain.model.TreeNode;
import com.onixbyte.helix.service.DepartmentService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
@RequestMapping("/departments")
public class DepartmentController {
private final DepartmentService departmentService;
public DepartmentController(DepartmentService departmentService) {
this.departmentService = departmentService;
}
@GetMapping("/tree")
public TreeNode<Department> getDepartmentTree() {
return departmentService.getDepartmentTree();
}
@GetMapping
public List<Department> getDepartments() {
return departmentService.getDepartments();
}
}
@@ -0,0 +1,97 @@
package com.onixbyte.helix.controller;
import com.onixbyte.helix.domain.web.response.BizExceptionResponse;
import com.onixbyte.helix.exception.BizException;
import jakarta.validation.ConstraintViolationException;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import java.time.LocalDateTime;
import java.util.stream.Collectors;
/**
* Global exception handler for the Helix application.
* <p>
* This controller advice provides centralised exception handling across all controllers in
* the application. It intercepts exceptions thrown during request processing and converts them into
* appropriate HTTP responses with standardised error formats.
* <p>
* The controller handles various types of exceptions including:
* <ul>
* <li>Business logic exceptions ({@link BizException})</li>
* <li>Bean validation constraint violations ({@link ConstraintViolationException})</li>
* </ul>
* <p>
* All error responses are formatted consistently using {@link BizExceptionResponse} to provide a
* uniform API error structure for client applications.
*
* @author zihluwang
* @see BizException
* @see BizExceptionResponse
* @see RestControllerAdvice
* @since 1.0.0
*/
@RestControllerAdvice
public class ExceptionController {
/**
* Handles business logic exceptions thrown throughout the application.
* <p>
* This method intercepts {@link BizException} instances and converts them into appropriate HTTP
* responses. The HTTP status code is determined by the exception's status property, whilst the
* error message is extracted from the exception and included in the response body.
* <p>
* The response includes a timestamp indicating when the error occurred and the specific error
* message describing the business logic violation.
*
* @param ex the business exception that was thrown
* @return a {@link ResponseEntity} containing the error response with appropriate HTTP status
* and {@link BizExceptionResponse} body
* @see BizException
* @see BizExceptionResponse
*/
@ExceptionHandler(BizException.class)
public ResponseEntity<BizExceptionResponse> handleBizException(BizException ex) {
return ResponseEntity.status(ex.getStatus())
.body(new BizExceptionResponse(
LocalDateTime.now(),
ex.getMessage())
);
}
/**
* Handles bean validation constraint violation exceptions.
* <p>
* This method processes {@link ConstraintViolationException} instances that occur when bean
* validation constraints are violated during request processing. It extracts all constraint
* violations, formats them into a readable error message, and returns a standardised
* error response.
* <p>
* The error message includes the property path and violation message for each constraint that
* was violated, separated by commas for multiple violations. The response is automatically
* assigned a {@code 400 Bad Request} status.
*
* @param ex the constraint violation exception containing validation errors
* @return a {@link BizExceptionResponse} containing the formatted validation
* error messages and timestamp
* @see ConstraintViolationException
* @see BizExceptionResponse
* @see jakarta.validation.constraints
*/
@ExceptionHandler(ConstraintViolationException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public BizExceptionResponse handleConstraintViolation(ConstraintViolationException ex) {
var errorMessage = ex.getConstraintViolations().stream()
.map((violation) -> violation.getPropertyPath() + ": " + violation.getMessage())
.collect(Collectors.joining(", "));
return new BizExceptionResponse(
LocalDateTime.now(),
errorMessage
);
}
}
@@ -0,0 +1,26 @@
package com.onixbyte.helix.controller;
import com.onixbyte.helix.domain.entity.Menu;
import com.onixbyte.helix.domain.model.TreeNode;
import com.onixbyte.helix.service.MenuService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
@RequestMapping("/menus")
public class MenuController {
private final MenuService menuService;
public MenuController(MenuService menuService) {
this.menuService = menuService;
}
@GetMapping
public List<TreeNode<Menu>> getMenuTree() {
return menuService.getMenuTree();
}
}
@@ -0,0 +1,31 @@
package com.onixbyte.helix.controller;
import com.onixbyte.helix.domain.entity.Position;
import com.onixbyte.helix.service.PositionService;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/positions")
public class PositionController {
private final PositionService positionService;
public PositionController(PositionService positionService) {
this.positionService = positionService;
}
@GetMapping
public Page<Position> getPositions(
@RequestParam(required = false, defaultValue = "1") Integer pageNum,
@RequestParam(required = false, defaultValue = "10") Integer pageSize
) {
var pageRequest = PageRequest.of(pageNum - 1, pageSize, Sort.by(Sort.Order.asc("id")));
return positionService.getPositions(pageRequest);
}
}
@@ -0,0 +1,31 @@
package com.onixbyte.helix.controller;
import com.onixbyte.helix.domain.entity.Role;
import com.onixbyte.helix.domain.web.request.QueryRoleRequest;
import com.onixbyte.helix.service.RoleService;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/roles")
public class RoleController {
private final RoleService roleService;
public RoleController(RoleService roleService) {
this.roleService = roleService;
}
@GetMapping
public Page<Role> getRoles(
@RequestParam(required = false, defaultValue = "1") Integer pageNum,
@RequestParam(required = false, defaultValue = "10") Integer pageSize,
@Validated @ModelAttribute QueryRoleRequest request
) {
var pageRequest = PageRequest.of(pageNum - 1, pageSize, Sort.by(Sort.Order.asc("id")));
return roleService.getRoles(pageRequest, request);
}
}
@@ -0,0 +1,14 @@
package com.onixbyte.helix.controller;
import com.onixbyte.helix.domain.entity.Setting;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping
public class SettingController {
}
@@ -0,0 +1,84 @@
package com.onixbyte.helix.controller;
import com.onixbyte.helix.domain.entity.User;
import com.onixbyte.helix.domain.web.request.AddUserRequest;
import com.onixbyte.helix.domain.web.request.QueryUserRequest;
import com.onixbyte.helix.domain.web.request.ResetPasswordRequest;
import com.onixbyte.helix.domain.web.request.UpdateUserRequest;
import com.onixbyte.helix.domain.web.response.UserDetailResponse;
import com.onixbyte.helix.service.UserService;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/users")
public class UserController {
private final UserService userService;
public UserController(UserService userService) {
this.userService = userService;
}
/**
* Get user list.
*
* @param pageNum page number
* @param pageSize page size
* @return paginated user list
*/
@PreAuthorize("hasAnyAuthority('system:user:read')")
@GetMapping
public Page<UserDetailResponse> queryUsers(
@RequestParam(required = false, defaultValue = "1") Integer pageNum,
@RequestParam(required = false, defaultValue = "10") Integer pageSize,
@Validated @ModelAttribute QueryUserRequest request
) {
var pageRequest = PageRequest.of(pageNum - 1, pageSize, Sort.by(Sort.Order.asc("id")));
return userService.queryUserDetailsPage(pageRequest, request);
}
/**
* Get user by user ID.
*
* @param userId user ID
* @return paginated user list
*/
@PreAuthorize("hasAnyAuthority('system:user:read')")
@GetMapping("/{userId:\\d+}")
public UserDetailResponse getUserDetailByUserId(@PathVariable Long userId) {
return userService.getUserDetailByUserId(userId);
}
@PostMapping
@PreAuthorize("hasAnyAuthority('system:user:write')")
public ResponseEntity<Void> addUser(@Validated @RequestBody AddUserRequest request) {
userService.addUser(request);
return ResponseEntity.ok(null);
}
@PutMapping
public ResponseEntity<Void> editUser(@Validated @RequestBody UpdateUserRequest request) {
userService.updateUser(request);
return ResponseEntity.ok(null);
}
@PreAuthorize("hasAnyAuthority('system:user:reset-password')")
@PatchMapping("/reset-password")
public ResponseEntity<Void> resetPassword(@Validated @RequestBody ResetPasswordRequest request) {
userService.resetPassword(request);
return ResponseEntity.ok(null);
}
@PreAuthorize("hasAnyAuthority('system:user:write')")
@DeleteMapping("/{userId:\\d+}")
public ResponseEntity<Void> deleteUser(@PathVariable Long userId) {
userService.deleteUser(userId);
return ResponseEntity.ok(null);
}
}
@@ -0,0 +1,34 @@
package com.onixbyte.helix.domain.common;
/**
* Represents an element that can be part of a tree structure.
* <p>
* This interface provides the basic methods necessary for an object to be identified,
* linked to a parent, and sorted within its siblings in a tree-like hierarchy.
*
* @param <K> the type of the key used for identification
*/
public interface Treeable<K> {
/**
* Retrieves the sort order value for this element amongst its siblings.
*
* @return the integer sort value
*/
Integer getSort();
/**
* Retrieves the unique identifier for this element.
*
* @return the element's unique key
*/
K getId();
/**
* Retrieves the identifier of this element's parent.
* <p>
* If the element is a root element, this method should return {@code null}.
*
* @return the key of the parent element, or {@code null} if it is a root
*/
K getParentId();
}
@@ -0,0 +1,44 @@
package com.onixbyte.helix.domain.database.query.wrapper;
import com.onixbyte.helix.constant.Status;
public class QueryRoleWrapper {
private String name;
private String code;
private Status status;
public QueryRoleWrapper() {
}
public QueryRoleWrapper(String name, String code, Status status) {
this.name = name;
this.code = code;
this.status = status;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public Status getStatus() {
return status;
}
public void setStatus(Status status) {
this.status = status;
}
}
@@ -0,0 +1,84 @@
package com.onixbyte.helix.domain.database.query.wrapper;
import com.onixbyte.helix.constant.UserStatus;
import java.time.LocalDateTime;
public class QueryUserWrapper {
private Long departmentId;
private String username;
private String regionAbbreviation;
private String phoneNumber;
private UserStatus status;
private LocalDateTime createdAtStart;
private LocalDateTime createdAtEnd;
public QueryUserWrapper() {
}
public QueryUserWrapper(Long departmentId, String username, String regionAbbreviation, String phoneNumber, UserStatus status, LocalDateTime createdAtStart, LocalDateTime createdAtEnd) {
this.departmentId = departmentId;
this.username = username;
this.regionAbbreviation = regionAbbreviation;
this.phoneNumber = phoneNumber;
this.status = status;
this.createdAtStart = createdAtStart;
this.createdAtEnd = createdAtEnd;
}
public Long getDepartmentId() {
return departmentId;
}
public void setDepartmentId(Long departmentId) {
this.departmentId = departmentId;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getRegionAbbreviation() {
return regionAbbreviation;
}
public void setRegionAbbreviation(String regionAbbreviation) {
this.regionAbbreviation = regionAbbreviation;
}
public String getPhoneNumber() {
return phoneNumber;
}
public void setPhoneNumber(String phoneNumber) {
this.phoneNumber = phoneNumber;
}
public UserStatus getStatus() {
return status;
}
public void setStatus(UserStatus status) {
this.status = status;
}
public LocalDateTime getCreatedAtStart() {
return createdAtStart;
}
public void setCreatedAtStart(LocalDateTime createdAtStart) {
this.createdAtStart = createdAtStart;
}
public LocalDateTime getCreatedAtEnd() {
return createdAtEnd;
}
public void setCreatedAtEnd(LocalDateTime createdAtEnd) {
this.createdAtEnd = createdAtEnd;
}
}
@@ -0,0 +1,126 @@
package com.onixbyte.helix.domain.entity;
import jakarta.persistence.*;
import java.time.LocalDateTime;
import java.util.Objects;
@Entity
@Table(name = "assets")
public class Asset {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(unique = true, nullable = false)
private String key;
@Column(nullable = false)
private Long uploadBy;
@Column(nullable = false)
private LocalDateTime uploadTime;
public Asset() {
}
public Asset(Long id, String key, Long uploadBy, LocalDateTime uploadTime) {
this.id = id;
this.key = key;
this.uploadBy = uploadBy;
this.uploadTime = uploadTime;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
public Long getUploadBy() {
return uploadBy;
}
public void setUploadBy(Long uploadBy) {
this.uploadBy = uploadBy;
}
public LocalDateTime getUploadTime() {
return uploadTime;
}
public void setUploadTime(LocalDateTime uploadTime) {
this.uploadTime = uploadTime;
}
@Override
public boolean equals(Object o) {
if (o == null || getClass() != o.getClass()) return false;
Asset that = (Asset) o;
return Objects.equals(id, that.id) && Objects.equals(key, that.key) && Objects.equals(uploadBy, that.uploadBy) && Objects.equals(uploadTime, that.uploadTime);
}
@Override
public int hashCode() {
return Objects.hash(id, key, uploadBy, uploadTime);
}
@Override
public String toString() {
return "Attachment{" +
"id=" + id +
", key='" + key + '\'' +
", uploadBy=" + uploadBy +
", uploadTime=" + uploadTime +
'}';
}
public static AssetBuilder builder() {
return new AssetBuilder();
}
public static class AssetBuilder {
private Long id;
private String key;
private Long uploadBy;
private LocalDateTime uploadTime;
private AssetBuilder() {
}
public AssetBuilder id(Long id) {
this.id = id;
return this;
}
public AssetBuilder key(String key) {
this.key = key;
return this;
}
public AssetBuilder uploadBy(Long uploadBy) {
this.uploadBy = uploadBy;
return this;
}
public AssetBuilder uploadTime(LocalDateTime uploadTime) {
this.uploadTime = uploadTime;
return this;
}
public Asset build() {
return new Asset(id, key, uploadBy, uploadTime);
}
}
}
@@ -0,0 +1,265 @@
package com.onixbyte.helix.domain.entity;
import com.onixbyte.helix.constant.Status;
import jakarta.persistence.*;
import org.hibernate.annotations.JdbcType;
import org.hibernate.annotations.Type;
import org.hibernate.dialect.PostgreSQLEnumJdbcType;
import org.springframework.security.core.GrantedAuthority;
import java.time.LocalDateTime;
import java.util.Objects;
/**
* Represents an authority (permission) entity within the access control system.
* <p>
* Authorities define specific permissions or capabilities that can be granted to roles. They
* represent the finest level of access control granularity, allowing for precise
* permission management. Authorities are typically associated with specific actions or resources
* within the application.
*
* @author zihluwang
* @version 1.0
* @since 1.0
*/
@Entity
@Table(name = "authorities")
public class Authority {
/**
* The unique identifier for the authority.
* <p>
* This serves as the primary key in the database and is used for all internal references to the
* authority entity.
*/
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
/**
* The unique code identifier for the authority.
* <p>
* This field contains a system-level identifier that uniquely identifies the permission. It is
* typically used in code for permission checks and should follow a consistent naming convention
* (e.g., "USER_READ", "ADMIN_WRITE").
*/
@Column(unique = true, nullable = false, length = 128)
private String code;
/**
* The human-readable name of the authority.
* <p>
* This field contains the display name of the authority as it should appear in user interfaces
* and administrative panels for permission management.
*/
@Column(nullable = false, length = 128)
private String name;
/**
* A detailed description of what this authority grants.
* <p>
* This field provides additional context about what specific permissions or capabilities this
* authority represents, helping administrators understand the implications of granting
* this authority.
*/
@Column
private String description;
/**
* The current status of the authority.
* <p>
* This field determines whether the authority is active, inactive, or in any other state as
* defined by the {@link Status} enumeration.
*/
@Column(nullable = false)
@Enumerated
@JdbcType(PostgreSQLEnumJdbcType.class)
private Status status;
/**
* The timestamp when this authority record was created.
* <p>
* This field is automatically set when the authority entity is first persisted and provides
* audit information about when the authority was established.
*/
@Column
private LocalDateTime createdAt;
/**
* The timestamp when this authority record was last updated.
* <p>
* This field is automatically updated whenever any changes are made to the authority entity and
* provides audit information about the most recent modification.
*/
@Column
private LocalDateTime updatedAt;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public Status getStatus() {
return status;
}
public void setStatus(Status status) {
this.status = status;
}
public LocalDateTime getCreatedAt() {
return createdAt;
}
public void setCreatedAt(LocalDateTime createdAt) {
this.createdAt = createdAt;
}
public LocalDateTime getUpdatedAt() {
return updatedAt;
}
public void setUpdatedAt(LocalDateTime updatedAt) {
this.updatedAt = updatedAt;
}
public Authority() {
}
public Authority(Long id, String code, String name, String description, Status status, LocalDateTime createdAt, LocalDateTime updatedAt) {
this.id = id;
this.code = code;
this.name = name;
this.description = description;
this.status = status;
this.createdAt = createdAt;
this.updatedAt = updatedAt;
}
@Override
public boolean equals(Object o) {
if (o == null || getClass() != o.getClass()) return false;
Authority authority = (Authority) o;
return Objects.equals(id, authority.id) && Objects.equals(code, authority.code) && Objects.equals(name, authority.name) && Objects.equals(description, authority.description) && status == authority.status && Objects.equals(createdAt, authority.createdAt) && Objects.equals(updatedAt, authority.updatedAt);
}
@Override
public int hashCode() {
return Objects.hash(id, code, name, description, status, createdAt, updatedAt);
}
@Override
public String toString() {
return "Authority{" +
"id=" + id +
", code='" + code + '\'' +
", name='" + name + '\'' +
", description='" + description + '\'' +
", status=" + status +
", createdAt=" + createdAt +
", updatedAt=" + updatedAt +
'}';
}
/**
* Creates a new Builder instance for constructing Authority objects.
*
* @return a new AuthorityBuilder instance
*/
public static AuthorityBuilder builder() {
return new AuthorityBuilder();
}
/**
* Builder class for constructing Authority instances with a fluent API.
* <p>
* This builder provides a convenient way to construct Authority objects with optional parameters,
* following the Builder pattern for improved readability and maintainability.
*/
public static class AuthorityBuilder {
private Long id;
private String code;
private String name;
private String description;
private Status status;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
private AuthorityBuilder() {
}
public AuthorityBuilder id(Long id) {
this.id = id;
return this;
}
public AuthorityBuilder code(String code) {
this.code = code;
return this;
}
public AuthorityBuilder name(String name) {
this.name = name;
return this;
}
public AuthorityBuilder description(String description) {
this.description = description;
return this;
}
public AuthorityBuilder status(Status status) {
this.status = status;
return this;
}
public AuthorityBuilder createdAt(LocalDateTime createdAt) {
this.createdAt = createdAt;
return this;
}
public AuthorityBuilder updatedAt(LocalDateTime updatedAt) {
this.updatedAt = updatedAt;
return this;
}
/**
* Builds and returns a new Authority instance with the configured properties.
*
* @return a new Authority instance
*/
public Authority build() {
return new Authority(id, code, name, description, status, createdAt, updatedAt);
}
}
public GrantedAuthority asGrantedAuthority() {
return this::getCode;
}
}
@@ -0,0 +1,290 @@
package com.onixbyte.helix.domain.entity;
import com.onixbyte.helix.constant.Status;
import com.onixbyte.helix.domain.common.Treeable;
import jakarta.persistence.*;
import org.hibernate.annotations.JdbcType;
import org.hibernate.dialect.PostgreSQLEnumJdbcType;
import java.time.LocalDateTime;
import java.util.Objects;
/**
* Represents a department entity within the organisational hierarchy.
* <p>
* This entity models departments as hierarchical structures where each department can have a
* parent department, creating a tree-like organisational structure. Departments are used to group
* users and define organisational boundaries within the Helix system.
* </p>
*
* @author zihluwang
* @version 1.0
* @since 1.0
*/
@Entity
@Table(name = "departments")
public class Department implements Treeable<Long> {
/**
* The unique identifier for the department.
* <p>
* This serves as the primary key in the database and is used for all
* internal references to the department entity.
* </p>
*/
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
/**
* The name of the department.
* <p>
* This field contains the human-readable name of the department as it
* should appear in the organisational chart and user interfaces.
* </p>
*/
@Column(length = 128, nullable = false)
private String name;
/**
* The identifier of the parent department.
* <p>
* This field establishes the hierarchical relationship between departments.
* A null value indicates that this is a root-level department with no parent.
* </p>
*/
@Column(nullable = false)
private Long parentId;
/**
* The sort order for displaying departments.
* <p>
* This field determines the order in which departments should be displayed
* when listed alongside their siblings in the hierarchy. Lower values
* indicate higher priority in sorting.
* </p>
*/
@Column(nullable = false)
private Integer sort;
/**
* The current status of the department.
* <p>
* This field determines whether the department is active, inactive, or in any
* other state as defined by the {@link Status} enumeration.
* </p>
*/
@Column(nullable = false)
@Enumerated
@JdbcType(PostgreSQLEnumJdbcType.class)
private Status status;
/**
* The timestamp when this department record was created.
* <p>
* This field is automatically set when the department entity is first persisted
* and provides audit information about when the department was established.
* </p>
*/
@Column
private LocalDateTime createdAt;
/**
* The timestamp when this department record was last updated.
* <p>
* This field is automatically updated whenever any changes are made to the
* department entity and provides audit information about the most recent modification.
* </p>
*/
@Column
private LocalDateTime updatedAt;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Long getParentId() {
return parentId;
}
public void setParentId(Long parentId) {
this.parentId = parentId;
}
public Integer getSort() {
return sort;
}
public void setSort(Integer sort) {
this.sort = sort;
}
public Status getStatus() {
return status;
}
public void setStatus(Status status) {
this.status = status;
}
public LocalDateTime getCreatedAt() {
return createdAt;
}
public void setCreatedAt(LocalDateTime createdAt) {
this.createdAt = createdAt;
}
public LocalDateTime getUpdatedAt() {
return updatedAt;
}
public void setUpdatedAt(LocalDateTime updatedAt) {
this.updatedAt = updatedAt;
}
/**
* Default constructor for Department.
* <p>
* Creates a new Department instance with all fields initialised to their default values.
* This constructor is typically used by JPA and other frameworks for entity instantiation.
* </p>
*/
public Department() {
}
/**
* Constructs a new Department with all specified parameters.
* <p>
* This constructor allows for the creation of a fully initialised Department entity
* with all field values provided at instantiation time.
* </p>
*
* @param id the unique identifier for the department
* @param name the name of the department
* @param parentId the identifier of the parent department (null for root departments)
* @param sort the sort order for display purposes
* @param status the current status of the department
* @param createdAt the timestamp when the department was created
* @param updatedAt the timestamp when the department was last updated
*/
public Department(Long id, String name, Long parentId, Integer sort, Status status, LocalDateTime createdAt, LocalDateTime updatedAt) {
this.id = id;
this.name = name;
this.parentId = parentId;
this.sort = sort;
this.status = status;
this.createdAt = createdAt;
this.updatedAt = updatedAt;
}
@Override
public boolean equals(Object o) {
if (o == null || getClass() != o.getClass()) return false;
Department that = (Department) o;
return Objects.equals(id, that.id) && Objects.equals(name, that.name) && Objects.equals(parentId, that.parentId) && Objects.equals(sort, that.sort) && status == that.status && Objects.equals(createdAt, that.createdAt) && Objects.equals(updatedAt, that.updatedAt);
}
@Override
public int hashCode() {
return Objects.hash(id, name, parentId, sort, status, createdAt, updatedAt);
}
@Override
public String toString() {
return "Department{" +
"id=" + id +
", name='" + name + '\'' +
", parentId=" + parentId +
", sort=" + sort +
", status=" + status +
", createdAt=" + createdAt +
", updatedAt=" + updatedAt +
'}';
}
/**
* Creates a new Builder instance for constructing Department objects.
*
* @return a new DepartmentBuilder instance
*/
public static DepartmentBuilder builder() {
return new DepartmentBuilder();
}
/**
* Builder class for constructing Department instances with a fluent API.
* <p>
* This builder provides a convenient way to construct Department objects with optional parameters,
* following the Builder pattern for improved readability and maintainability.
*/
public static class DepartmentBuilder {
private Long id;
private String name;
private Long parentId;
private Integer sort;
private Status status;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
private DepartmentBuilder() {
}
public DepartmentBuilder id(Long id) {
this.id = id;
return this;
}
public DepartmentBuilder name(String name) {
this.name = name;
return this;
}
public DepartmentBuilder parentId(Long parentId) {
this.parentId = parentId;
return this;
}
public DepartmentBuilder sort(Integer sort) {
this.sort = sort;
return this;
}
public DepartmentBuilder status(Status status) {
this.status = status;
return this;
}
public DepartmentBuilder createdAt(LocalDateTime createdAt) {
this.createdAt = createdAt;
return this;
}
public DepartmentBuilder updatedAt(LocalDateTime updatedAt) {
this.updatedAt = updatedAt;
return this;
}
/**
* Builds and returns a new Department instance with the configured properties.
*
* @return a new Department instance
*/
public Department build() {
return new Department(id, name, parentId, sort, status, createdAt, updatedAt);
}
}
}
@@ -0,0 +1,227 @@
package com.onixbyte.helix.domain.entity;
import com.onixbyte.helix.constant.Status;
import com.onixbyte.helix.domain.common.Treeable;
import jakarta.persistence.*;
import org.hibernate.annotations.JdbcType;
import org.hibernate.dialect.PostgreSQLEnumJdbcType;
import java.time.LocalDateTime;
import java.util.Objects;
@Entity
@Table(name = "menus")
public class Menu implements Treeable<Long> {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, length = 50)
private String name;
@Column
private Long parentId;
@Column(nullable = false)
private String code;
@Column(nullable = false)
private Integer sort;
@Column
private String path;
@Column
private Boolean isExternalLink;
@Column
private Boolean isVisible;
@Column(nullable = false)
@Enumerated
@JdbcType(PostgreSQLEnumJdbcType.class)
private Status status;
@Column(length = 128)
private String authorityCode;
@Column(length = 128)
private String icon;
@Column
private LocalDateTime createdAt;
@Column
private LocalDateTime updatedAt;
public Menu() {
}
public Menu(Long id, String name, Long parentId, String code, Integer sort, String path, Boolean isExternalLink, Boolean isVisible, Status status, String authorityCode, String icon, LocalDateTime createdAt, LocalDateTime updatedAt) {
this.id = id;
this.name = name;
this.parentId = parentId;
this.code = code;
this.sort = sort;
this.path = path;
this.isExternalLink = isExternalLink;
this.isVisible = isVisible;
this.status = status;
this.authorityCode = authorityCode;
this.icon = icon;
this.createdAt = createdAt;
this.updatedAt = updatedAt;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Long getParentId() {
return parentId;
}
public void setParentId(Long parentId) {
this.parentId = parentId;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public Boolean getExternalLink() {
return isExternalLink;
}
public void setExternalLink(Boolean externalLink) {
isExternalLink = externalLink;
}
public Boolean getVisible() {
return isVisible;
}
public void setVisible(Boolean visible) {
isVisible = visible;
}
public Integer getSort() {
return sort;
}
public void setSort(Integer sort) {
this.sort = sort;
}
public String getPath() {
return path;
}
public void setPath(String path) {
this.path = path;
}
public Boolean getIsExternalLink() {
return isExternalLink;
}
public void setIsExternalLink(Boolean isExternalLink) {
this.isExternalLink = isExternalLink;
}
public Boolean getIsVisible() {
return isVisible;
}
public void setIsVisible(Boolean isVisible) {
this.isVisible = isVisible;
}
public Status getStatus() {
return status;
}
public void setStatus(Status status) {
this.status = status;
}
public String getAuthorityCode() {
return authorityCode;
}
public void setAuthorityCode(String authorityCode) {
this.authorityCode = authorityCode;
}
public String getIcon() {
return icon;
}
public void setIcon(String icon) {
this.icon = icon;
}
public LocalDateTime getCreatedAt() {
return createdAt;
}
public void setCreatedAt(LocalDateTime createdAt) {
this.createdAt = createdAt;
}
public LocalDateTime getUpdatedAt() {
return updatedAt;
}
public void setUpdatedAt(LocalDateTime updatedAt) {
this.updatedAt = updatedAt;
}
@Override
public boolean equals(Object o) {
if (o == null || getClass() != o.getClass()) return false;
Menu menu = (Menu) o;
return Objects.equals(id, menu.id) && Objects.equals(name, menu.name) && Objects.equals(parentId, menu.parentId) && Objects.equals(code, menu.code) && Objects.equals(sort, menu.sort) && Objects.equals(path, menu.path) && Objects.equals(isExternalLink, menu.isExternalLink) && Objects.equals(isVisible, menu.isVisible) && status == menu.status && Objects.equals(authorityCode, menu.authorityCode) && Objects.equals(icon, menu.icon) && Objects.equals(createdAt, menu.createdAt) && Objects.equals(updatedAt, menu.updatedAt);
}
@Override
public int hashCode() {
return Objects.hash(id, name, parentId, code, sort, path, isExternalLink, isVisible, status, authorityCode, icon, createdAt, updatedAt);
}
@Override
public String toString() {
return "Menu{" +
"id=" + id +
", name='" + name + '\'' +
", parentId=" + parentId +
", code=" + code +
", sort=" + sort +
", path=" + path +
", isExternalLink=" + isExternalLink +
", isVisible=" + isVisible +
", status=" + status +
", authorityCode='" + authorityCode + '\'' +
", icon='" + icon + '\'' +
", createdAt=" + createdAt +
", updatedAt=" + updatedAt +
'}';
}
}
@@ -0,0 +1,284 @@
package com.onixbyte.helix.domain.entity;
import com.onixbyte.helix.constant.Status;
import jakarta.persistence.*;
import org.hibernate.annotations.JdbcType;
import org.hibernate.dialect.PostgreSQLEnumJdbcType;
import java.time.LocalDateTime;
import java.util.Objects;
/**
* Represents a position entity within the organisational structure.
* <p>
* Positions define job roles or titles that can be assigned to users within the organisation. They
* provide a way to categorise users based on their responsibilities and functions, complementing
* the department-based organisational hierarchy. Positions can be used for reporting,
* access control, and organisational management purposes.
*
* @author zihluwang
* @version 1.0
* @since 1.0
*/
@Entity
@Table(name = "positions")
public class Position {
/**
* The unique identifier for the position.
* <p>
* This serves as the primary key in the database and is used for all internal references to the
* position entity.
*/
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
/**
* The human-readable name of the position.
* <p>
* This field contains the job title or position name as it should appear in
* organisational charts, user profiles, and administrative interfaces.
*/
@Column(nullable = false, length = 128, unique = true)
private String name;
/**
* The unique code identifier for the position.
* <p>
* This field contains a system-level identifier that uniquely identifies the position. It is
* typically used for integration purposes and should follow a consistent naming convention.
*/
@Column(nullable = false, length = 64, unique = true)
private String code;
/**
* A detailed description of the position's responsibilities and requirements.
* <p>
* This field provides additional context about the role, including key responsibilities,
* required skills, or other relevant information that helps define what this position entails.
*/
@Column
private String description;
/**
* The sort order for displaying positions.
* <p>
* This field determines the order in which positions should be displayed in lists and
* selection interfaces. Lower values indicate higher priority in sorting, which can reflect
* organisational hierarchy or importance.
*/
@Column(nullable = false)
private Integer sort;
/**
* The current status of the position.
* <p>
* This field determines whether the position is active, inactive, or in any other state as
* defined by the {@link Status} enumeration.
*/
@Column(nullable = false)
@Enumerated
@JdbcType(PostgreSQLEnumJdbcType.class)
private Status status;
/**
* The timestamp when this position record was created.
* <p>
* This field is automatically set when the position entity is first persisted and provides
* audit information about when the position was established.
*/
@Column
private LocalDateTime createdAt;
/**
* The timestamp when this position record was last updated.
* <p>
* This field is automatically updated whenever any changes are made to the position entity and
* provides audit information about the most recent modification.
*/
@Column
private LocalDateTime updatedAt;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public Integer getSort() {
return sort;
}
public void setSort(Integer sort) {
this.sort = sort;
}
public Status getStatus() {
return status;
}
public void setStatus(Status status) {
this.status = status;
}
public LocalDateTime getCreatedAt() {
return createdAt;
}
public void setCreatedAt(LocalDateTime createdAt) {
this.createdAt = createdAt;
}
public LocalDateTime getUpdatedAt() {
return updatedAt;
}
public void setUpdatedAt(LocalDateTime updatedAt) {
this.updatedAt = updatedAt;
}
public Position() {
}
public Position(Long id, String name, String code, String description, Integer sort, Status status, LocalDateTime createdAt, LocalDateTime updatedAt) {
this.id = id;
this.name = name;
this.code = code;
this.description = description;
this.sort = sort;
this.status = status;
this.createdAt = createdAt;
this.updatedAt = updatedAt;
}
@Override
public boolean equals(Object o) {
if (o == null || getClass() != o.getClass()) return false;
Position position = (Position) o;
return Objects.equals(id, position.id) && Objects.equals(name, position.name) && Objects.equals(code, position.code) && Objects.equals(description, position.description) && Objects.equals(sort, position.sort) && status == position.status && Objects.equals(createdAt, position.createdAt) && Objects.equals(updatedAt, position.updatedAt);
}
@Override
public int hashCode() {
return Objects.hash(id, name, code, description, sort, status, createdAt, updatedAt);
}
@Override
public String toString() {
return "Position{" +
"id=" + id +
", name='" + name + '\'' +
", code='" + code + '\'' +
", description='" + description + '\'' +
", sort=" + sort +
", status=" + status +
", createdAt=" + createdAt +
", updatedAt=" + updatedAt +
'}';
}
/**
* Creates a new Builder instance for constructing Position objects.
*
* @return a new PositionBuilder instance
*/
public static PositionBuilder builder() {
return new PositionBuilder();
}
/**
* Builder class for constructing Position instances with a fluent API.
* <p>
* This builder provides a convenient way to construct Position objects with optional parameters,
* following the Builder pattern for improved readability and maintainability.
*/
public static class PositionBuilder {
private Long id;
private String name;
private String code;
private String description;
private Integer sort;
private Status status;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
private PositionBuilder() {
}
public PositionBuilder id(Long id) {
this.id = id;
return this;
}
public PositionBuilder name(String name) {
this.name = name;
return this;
}
public PositionBuilder code(String code) {
this.code = code;
return this;
}
public PositionBuilder description(String description) {
this.description = description;
return this;
}
public PositionBuilder sort(Integer sort) {
this.sort = sort;
return this;
}
public PositionBuilder status(Status status) {
this.status = status;
return this;
}
public PositionBuilder createdAt(LocalDateTime createdAt) {
this.createdAt = createdAt;
return this;
}
public PositionBuilder updatedAt(LocalDateTime updatedAt) {
this.updatedAt = updatedAt;
return this;
}
/**
* Builds and returns a new Position instance with the configured properties.
*
* @return a new Position instance
*/
public Position build() {
return new Position(id, name, code, description, sort, status, createdAt, updatedAt);
}
}
}
@@ -0,0 +1,308 @@
package com.onixbyte.helix.domain.entity;
import com.onixbyte.helix.constant.Status;
import jakarta.persistence.*;
import org.hibernate.annotations.JdbcType;
import org.hibernate.dialect.PostgreSQLEnumJdbcType;
import java.time.LocalDateTime;
import java.util.Objects;
/**
* Represents a role entity within the access control system.
* <p>
* Roles define sets of permissions and responsibilities that can be assigned to users. They form
* the foundation of the role-based access control (RBAC) system, allowing for flexible and scalable
* permission management across the Helix application.
*
* @author zihluwang
* @version 1.0
* @since 1.0
*/
@Entity
@Table(name = "roles")
public class Role {
/**
* The unique identifier for the role.
* <p>
* This serves as the primary key in the database and is used for all internal references to the
* role entity.
*/
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
/**
* The human-readable name of the role.
* <p>
* This field contains the display name of the role as it should appear in user interfaces and
* administrative panels.
*/
@Column(nullable = false, unique = true, length = 128)
private String name;
/**
* The unique code identifier for the role.
* <p>
* This field contains a system-level identifier that is typically used in code
* and configuration. It should be unique across all roles and follow a consistent
* naming convention.
*/
@Column(nullable = false, unique = true, length = 64)
private String code;
/**
* The sort order for displaying roles.
* <p>
* This field determines the order in which roles should be displayed in lists and
* selection interfaces. Lower values indicate higher priority in sorting.
*/
@Column(nullable = false)
private Integer sort;
/**
* Indicates whether this role is assigned by default to new users.
* <p>
* When set to true, this role will be automatically assigned to newly created user accounts.
* This is useful for defining baseline permissions that all users should have.
*/
@Column
private Boolean defaultValue;
/**
* A detailed description of the role's purpose and permissions.
* <p>
* This field provides additional context about what the role represents and what capabilities
* it grants to users who are assigned to it.
*/
@Column
private String description;
/**
* The current status of the role.
* <p>
* This field determines whether the role is active, inactive, or in any other state as defined
* by the {@link Status} enumeration.
*/
@Column(nullable = false)
@Enumerated
@JdbcType(PostgreSQLEnumJdbcType.class)
private Status status;
/**
* The timestamp when this role record was created.
* <p>
* This field is automatically set when the role entity is first persisted and provides audit
* information about when the role was established.
*/
@Column
private LocalDateTime createdAt;
/**
* The timestamp when this role record was last updated.
* <p>
* This field is automatically updated whenever any changes are made to the role entity and
* provides audit information about the most recent modification.
*/
@Column
private LocalDateTime updatedAt;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public Integer getSort() {
return sort;
}
public void setSort(Integer sort) {
this.sort = sort;
}
public Boolean getDefaultValue() {
return defaultValue;
}
public void setDefaultValue(Boolean defaultValue) {
this.defaultValue = defaultValue;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public Status getStatus() {
return status;
}
public void setStatus(Status status) {
this.status = status;
}
public LocalDateTime getCreatedAt() {
return createdAt;
}
public void setCreatedAt(LocalDateTime createdAt) {
this.createdAt = createdAt;
}
public LocalDateTime getUpdatedAt() {
return updatedAt;
}
public void setUpdatedAt(LocalDateTime updatedAt) {
this.updatedAt = updatedAt;
}
public Role() {
}
public Role(Long id, String name, String code, Integer sort, Boolean defaultValue, String description, Status status, LocalDateTime createdAt, LocalDateTime updatedAt) {
this.id = id;
this.name = name;
this.code = code;
this.sort = sort;
this.defaultValue = defaultValue;
this.description = description;
this.status = status;
this.createdAt = createdAt;
this.updatedAt = updatedAt;
}
@Override
public boolean equals(Object o) {
if (o == null || getClass() != o.getClass()) return false;
Role role = (Role) o;
return Objects.equals(id, role.id) && Objects.equals(name, role.name) && Objects.equals(code, role.code) && Objects.equals(sort, role.sort) && Objects.equals(defaultValue, role.defaultValue) && Objects.equals(description, role.description) && status == role.status && Objects.equals(createdAt, role.createdAt) && Objects.equals(updatedAt, role.updatedAt);
}
@Override
public int hashCode() {
return Objects.hash(id, name, code, sort, defaultValue, description, status, createdAt, updatedAt);
}
@Override
public String toString() {
return "Role{" +
"id=" + id +
", name='" + name + '\'' +
", code='" + code + '\'' +
", sort=" + sort +
", defaultValue=" + defaultValue +
", description='" + description + '\'' +
", status=" + status +
", createdAt=" + createdAt +
", updatedAt=" + updatedAt +
'}';
}
/**
* Creates a new Builder instance for constructing Role objects.
*
* @return a new RoleBuilder instance
*/
public static RoleBuilder builder() {
return new RoleBuilder();
}
/**
* Builder class for constructing Role instances with a fluent API.
* <p>
* This builder provides a convenient way to construct Role objects with optional parameters,
* following the Builder pattern for improved readability and maintainability.
*/
public static class RoleBuilder {
private Long id;
private String name;
private String code;
private Integer sort;
private Boolean defaultValue;
private String description;
private Status status;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
private RoleBuilder() {
}
public RoleBuilder id(Long id) {
this.id = id;
return this;
}
public RoleBuilder name(String name) {
this.name = name;
return this;
}
public RoleBuilder code(String code) {
this.code = code;
return this;
}
public RoleBuilder sort(Integer sort) {
this.sort = sort;
return this;
}
public RoleBuilder defaultValue(Boolean defaultValue) {
this.defaultValue = defaultValue;
return this;
}
public RoleBuilder description(String description) {
this.description = description;
return this;
}
public RoleBuilder status(Status status) {
this.status = status;
return this;
}
public RoleBuilder createdAt(LocalDateTime createdAt) {
this.createdAt = createdAt;
return this;
}
public RoleBuilder updatedAt(LocalDateTime updatedAt) {
this.updatedAt = updatedAt;
return this;
}
/**
* Builds and returns a new Role instance with the configured properties.
*
* @return a new Role instance
*/
public Role build() {
return new Role(id, name, code, sort, defaultValue, description, status, createdAt, updatedAt);
}
}
}
@@ -0,0 +1,125 @@
package com.onixbyte.helix.domain.entity;
import com.onixbyte.helix.domain.entity.embeddable.RoleAuthorityId;
import jakarta.persistence.*;
import java.time.LocalDateTime;
import java.util.Objects;
/**
* Represents the association entity between a Role and an Authority in a many-to-many relationship.
* <p>
* This entity is mapped to the 'role_authorities' table and uses a composite primary key
* {@code @EmbeddedId} defined in the RoleAuthorityId class. It also includes the 'createdAt'
* auditing field.
*/
@Entity
@Table(name = "role_authorities")
public class RoleAuthority {
/**
* The composite primary key of the association, mapped to the 'role_id' and 'authority_id' columns.
* <p>
* This field embeds the RoleAuthorityId object which holds the foreign keys to the Role and Authority tables.
*/
@EmbeddedId
private RoleAuthorityId id;
/**
* The timestamp when this role-authority association was created.
* <p>
* This field is automatically set upon creation in the database. {@code @Column} maps the field
* to the 'created_at' column.
*/
@Column(nullable = false, updatable = false)
private LocalDateTime createdAt;
public Long getRoleId() {
return this.id != null ? this.id.getRoleId() : null;
}
public Long getAuthorityId() {
return this.id != null ? this.id.getAuthorityId() : null;
}
public RoleAuthorityId getId() {
return id;
}
public void setId(RoleAuthorityId id) {
this.id = id;
}
public LocalDateTime getCreatedAt() {
return createdAt;
}
public void setCreatedAt(LocalDateTime createdAt) {
this.createdAt = createdAt;
}
public RoleAuthority() {
// Initialise the ID for safety when created via JPA
this.id = new RoleAuthorityId();
}
public RoleAuthority(Long roleId, Long authorityId, LocalDateTime createdAt) {
this.id = new RoleAuthorityId(roleId, authorityId);
this.createdAt = createdAt;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
RoleAuthority that = (RoleAuthority) o;
// Only check the primary key for entity equality
return Objects.equals(id, that.id);
}
@Override
public int hashCode() {
// Hash code based on the primary key
return Objects.hash(id);
}
@Override
public String toString() {
return "RoleAuthority{" +
"roleId=" + (id != null ? id.getRoleId() : "null") +
", authorityId=" + (id != null ? id.getAuthorityId() : "null") +
", createdAt=" + createdAt +
'}';
}
public static RoleAuthorityBuilder builder() {
return new RoleAuthorityBuilder();
}
public static class RoleAuthorityBuilder {
private Long roleId;
private Long authorityId;
private LocalDateTime createdAt;
private RoleAuthorityBuilder() {
}
public RoleAuthorityBuilder roleId(Long roleId) {
this.roleId = roleId;
return this;
}
public RoleAuthorityBuilder authorityId(Long authorityId) {
this.authorityId = authorityId;
return this;
}
public RoleAuthorityBuilder createdAt(LocalDateTime createdAt) {
this.createdAt = createdAt;
return this;
}
public RoleAuthority build() {
return new RoleAuthority(roleId, authorityId, createdAt);
}
}
}
@@ -0,0 +1,269 @@
package com.onixbyte.helix.domain.entity;
import com.onixbyte.helix.constant.SettingType;
import jakarta.persistence.*;
import org.hibernate.annotations.JdbcType;
import org.hibernate.dialect.PostgreSQLEnumJdbcType;
import java.time.LocalDateTime;
import java.util.Objects;
/**
* Represents a hot-deployable application setting, stored in the 'settings' database table.
* <p>
* This entity allows for dynamic configuration changes without application restarts.
*/
@Entity
@Table(name = "settings")
public class Setting {
/**
* Setting unique identifier, mapped to the primary key 'id'.
* <p>
* Uses BIGSERIAL (Long) and is set to auto-increment by the database.
*/
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
/**
* Setting name. Used as a unique key for retrieval.
*/
@Column(nullable = false, unique = true)
private String name;
/**
* Setting description.
*/
@Column
private String description;
/**
* The type of the value (e.g., BOOLEAN, INT, STRING).
* <p>
* Mapped to the custom SQL type SETTING_TYPE, typically handled by JPA as an Enum.
*/
@Column(nullable = false)
@Enumerated
@JdbcType(PostgreSQLEnumJdbcType.class)
private SettingType type;
/**
* Setting current value. Stored as a string regardless of the actual type.
*/
@Column
private String value;
/**
* Setting default value.
*/
@Column(nullable = false)
private String defaultValue;
/**
* The timestamp when this setting was created.
* <p>
* Set only on creation and remains unchanged.
*/
@Column(nullable = false, updatable = false)
private LocalDateTime createdAt;
/**
* The timestamp when this setting was last updated.
* <p>
* Updated on every change to the entity.
*/
@Column(nullable = false)
private LocalDateTime updatedAt;
@PrePersist
protected void onCreate() {
this.createdAt = LocalDateTime.now();
this.updatedAt = LocalDateTime.now();
}
@PreUpdate
protected void onUpdate() {
this.updatedAt = LocalDateTime.now();
}
public Setting() {
}
public Setting(Long id, String name, String description, SettingType type, String value, String defaultValue, LocalDateTime createdAt, LocalDateTime updatedAt) {
this.id = id;
this.name = name;
this.description = description;
this.type = type;
this.value = value;
this.defaultValue = defaultValue;
this.createdAt = createdAt;
this.updatedAt = updatedAt;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public SettingType getType() {
return type;
}
public void setType(SettingType type) {
this.type = type;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
public String getDefaultValue() {
return defaultValue;
}
public void setDefaultValue(String defaultValue) {
this.defaultValue = defaultValue;
}
public LocalDateTime getCreatedAt() {
return createdAt;
}
public void setCreatedAt(LocalDateTime createdAt) {
this.createdAt = createdAt;
}
public LocalDateTime getUpdatedAt() {
return updatedAt;
}
public void setUpdatedAt(LocalDateTime updatedAt) {
this.updatedAt = updatedAt;
}
public String fetchValueOrDefault() {
if (Objects.nonNull(value) && !value.isBlank()) {
return value;
}
return defaultValue;
}
public Boolean asBoolean() {
var val = fetchValueOrDefault();
if (type == SettingType.BOOLEAN) {
return Boolean.parseBoolean(val);
} else {
return null;
}
}
public Integer asInt() {
try {
var val = fetchValueOrDefault();
if (type == SettingType.INT) {
return Integer.parseInt(val);
}
return null;
} catch (NumberFormatException e) {
return null;
}
}
public Long asLong() {
try {
var val = fetchValueOrDefault();
if (type == SettingType.INT) {
return Long.parseLong(val);
}
return null;
} catch (NumberFormatException e) {
return null;
}
}
public static SettingBuilder builder() {
return new SettingBuilder();
}
public static class SettingBuilder {
private Long id;
private String name;
private String description;
private SettingType type;
private String value;
private String defaultValue;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
private SettingBuilder() {
}
public SettingBuilder id(Long id) {
this.id = id;
return this;
}
public SettingBuilder name(String name) {
this.name = name;
return this;
}
public SettingBuilder description(String description) {
this.description = description;
return this;
}
public SettingBuilder type(SettingType type) {
this.type = type;
return this;
}
public SettingBuilder value(String value) {
this.value = value;
return this;
}
public SettingBuilder defaultValue(String defaultValue) {
this.defaultValue = defaultValue;
return this;
}
public SettingBuilder createdAt(LocalDateTime createdAt) {
this.createdAt = createdAt;
return this;
}
public SettingBuilder updatedAt(LocalDateTime updatedAt) {
this.updatedAt = updatedAt;
return this;
}
public Setting build() {
return new Setting(id, name, description, type, value, defaultValue, createdAt, updatedAt);
}
}
}
@@ -0,0 +1,453 @@
package com.onixbyte.helix.domain.entity;
import com.onixbyte.helix.constant.UserStatus;
import jakarta.persistence.*; // 导入 Jakarta Persistence API
import org.hibernate.annotations.JdbcType;
import org.hibernate.dialect.PostgreSQLEnumJdbcType;
import java.time.LocalDateTime;
import java.util.Objects;
/**
* Represents a user entity in the Helix system.
* <p>
* This entity encapsulates all user-related information including authentication credentials,
* personal details, contact information, and organisational associations. Users are the core
* entities that interact with the system and are associated with departments and positions within
* the organisational hierarchy.
*
* @author zihluwang
* @version 1.0
* @since 1.0
*/
@Entity
@Table(
name = "users",
uniqueConstraints = {
@UniqueConstraint(name = "uidx_users_username", columnNames = {"username"}),
@UniqueConstraint(name = "uidx_users_email", columnNames = {"email"}),
@UniqueConstraint(name = "uidx_users_region_abbreviation_phone_number", columnNames = {"region_abbreviation", "phone_number"})
},
indexes = {@Index(name = "users_username_index", columnList = "username")}
)
public class User {
/**
* The unique identifier for the user.
* <p>
* This serves as the primary key in the database and is used for all internal references to the
* user entity. Since the SQL uses `BIGINT PRIMARY KEY` without `SERIAL`, we assume the ID is assigned manually or by an external service.
*/
@Id
@Column(nullable = false)
private Long id;
/**
* The unique username for authentication purposes.
*/
@Column(nullable = false, length = 64)
private String username;
/**
* The encrypted password for user authentication.
*/
@Column
private String password;
/**
* The user's complete full name.
*/
@Column(nullable = false, length = 128)
private String fullName;
/**
* The user's email address.
*/
@Column(length = 128)
private String email;
/**
* The region abbreviation for the user's phone number.
*/
@Column(length = 10)
private String regionAbbreviation;
/**
* The user's phone number without the country code.
*/
@Column(length = 32)
private String phoneNumber;
/**
* The URL to the user's avatar image.
*/
@Column(columnDefinition = "TEXT")
private String avatarUrl;
/**
* The current status of the user account.
* <p>
* Mapped to the custom SQL type USER_STATUS.
*/
@Column(nullable = false)
@Enumerated
@JdbcType(PostgreSQLEnumJdbcType.class)
private UserStatus status;
/**
* The identifier of the department to which this user belongs.
* <p>
* {@code @ManyToOne} is the standard way to map a foreign key (department_id) in JPA.
* You might replace this with a direct {@code @ManyToOne} mapping to the Department entity later.
*/
@Column
private Long departmentId;
/**
* The identifier of the position held by this user.
*/
@Column
private Long positionId;
/**
* The timestamp when this user record was created.
*/
@Column(nullable = false, updatable = false)
private LocalDateTime createdAt;
/**
* The timestamp when this user record was last updated.
*/
@Column(nullable = false)
private LocalDateTime updatedAt;
// --- JPA Lifecycle Callbacks for Auditing ---
@PrePersist
protected void onCreate() {
if (this.createdAt == null) {
this.createdAt = LocalDateTime.now();
}
if (this.updatedAt == null) {
this.updatedAt = LocalDateTime.now();
}
// Ensure status default is applied if not set
if (this.status == null) {
this.status = UserStatus.ACTIVE;
}
}
@PreUpdate
protected void onUpdate() {
this.updatedAt = LocalDateTime.now();
}
// --- Constructors, Getters, Setters, and Builder (omitted for brevity) ---
public User() {
}
public User(Long id, String username, String password, String fullName, String email, String regionAbbreviation, String phoneNumber, String avatarUrl, UserStatus status, Long departmentId, Long positionId, LocalDateTime createdAt, LocalDateTime updatedAt) {
this.id = id;
this.username = username;
this.password = password;
this.fullName = fullName;
this.email = email;
this.regionAbbreviation = regionAbbreviation;
this.phoneNumber = phoneNumber;
this.avatarUrl = avatarUrl;
this.status = status;
this.departmentId = departmentId;
this.positionId = positionId;
this.createdAt = createdAt;
this.updatedAt = updatedAt;
}
/**
* Gets the unique identifier for this user.
*
* @return the user's unique identifier
*/
public Long getId() {
return id;
}
/**
* Sets the unique identifier for this user.
*
* @param id the user's unique identifier
*/
public void setId(Long id) {
this.id = id;
}
/**
* Gets the username for authentication.
*
* @return the username
*/
public String getUsername() {
return username;
}
/**
* Sets the username for authentication.
*
* @param username the username, must be unique across the system
*/
public void setUsername(String username) {
this.username = username;
}
/**
* Gets the encrypted password.
*
* @return the encrypted password
*/
public String getPassword() {
return password;
}
/**
* Sets the encrypted password.
*
* @param password the encrypted password (never plain text)
*/
public void setPassword(String password) {
this.password = password;
}
public String getFullName() {
return fullName;
}
public void setFullName(String fullName) {
this.fullName = fullName;
}
/**
* Gets the user's email address.
*
* @return the email address
*/
public String getEmail() {
return email;
}
/**
* Sets the user's email address.
*
* @param email the email address, must be unique across the system
*/
public void setEmail(String email) {
this.email = email;
}
public String getRegionAbbreviation() {
return regionAbbreviation;
}
public void setRegionAbbreviation(String countryCode) {
this.regionAbbreviation = countryCode;
}
public String getPhoneNumber() {
return phoneNumber;
}
public void setPhoneNumber(String phoneNumber) {
this.phoneNumber = phoneNumber;
}
public String getAvatarUrl() {
return avatarUrl;
}
public void setAvatarUrl(String avatarUrl) {
this.avatarUrl = avatarUrl;
}
/**
* Gets the current status of the user account.
*
* @return the user status
*/
public UserStatus getStatus() {
return status;
}
/**
* Sets the current status of the user account.
*
* @param status the user status
*/
public void setStatus(UserStatus status) {
this.status = status;
}
public Long getDepartmentId() {
return departmentId;
}
public void setDepartmentId(Long departmentId) {
this.departmentId = departmentId;
}
public Long getPositionId() {
return positionId;
}
public void setPositionId(Long positionId) {
this.positionId = positionId;
}
public LocalDateTime getCreatedAt() {
return createdAt;
}
public void setCreatedAt(LocalDateTime createdAt) {
this.createdAt = createdAt;
}
public LocalDateTime getUpdatedAt() {
return updatedAt;
}
public void setUpdatedAt(LocalDateTime updatedAt) {
this.updatedAt = updatedAt;
}
@Override
public boolean equals(Object o) {
if (o == null || getClass() != o.getClass()) return false;
User user = (User) o;
return Objects.equals(id, user.id); // Typically only check the Primary Key for entity equality
}
@Override
public int hashCode() {
return Objects.hash(id); // Hash code based on the Primary Key
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", fullName='" + fullName + '\'' +
", email='" + email + '\'' +
", status=" + status +
", departmentId=" + departmentId +
", positionId=" + positionId +
'}';
}
/**
* Creates a new Builder instance for constructing User objects.
*
* @return a new UserBuilder instance
*/
public static UserBuilder builder() {
return new UserBuilder();
}
/**
* Builder class for constructing User instances with a fluent API.
* <p>
* This builder provides a convenient way to construct User objects with optional parameters,
* following the Builder pattern for improved readability and maintainability.
*/
public static class UserBuilder {
private Long id;
private String username;
private String password;
private String fullName;
private String email;
private String regionAbbreviation;
private String phoneNumber;
private String avatarUrl;
private UserStatus status;
private Long departmentId;
private Long positionId;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
private UserBuilder() {
}
public UserBuilder id(Long id) {
this.id = id;
return this;
}
public UserBuilder username(String username) {
this.username = username;
return this;
}
public UserBuilder password(String password) {
this.password = password;
return this;
}
public UserBuilder fullName(String fullName) {
this.fullName = fullName;
return this;
}
public UserBuilder email(String email) {
this.email = email;
return this;
}
public UserBuilder regionAbbreviation(String regionAbbreviation) {
this.regionAbbreviation = regionAbbreviation;
return this;
}
public UserBuilder phoneNumber(String phoneNumber) {
this.phoneNumber = phoneNumber;
return this;
}
public UserBuilder avatarUrl(String avatarUrl) {
this.avatarUrl = avatarUrl;
return this;
}
public UserBuilder status(UserStatus status) {
this.status = status;
return this;
}
public UserBuilder departmentId(Long departmentId) {
this.departmentId = departmentId;
return this;
}
public UserBuilder positionId(Long positionId) {
this.positionId = positionId;
return this;
}
public UserBuilder createdAt(LocalDateTime createdAt) {
this.createdAt = createdAt;
return this;
}
public UserBuilder updatedAt(LocalDateTime updatedAt) {
this.updatedAt = updatedAt;
return this;
}
/**
* Builds and returns a new User instance with the configured properties.
*
* @return a new User instance
*/
public User build() {
return new User(id, username, password, fullName, email, regionAbbreviation, phoneNumber, avatarUrl, status, departmentId, positionId, createdAt, updatedAt);
}
}
}
@@ -0,0 +1,227 @@
package com.onixbyte.helix.domain.entity;
import com.onixbyte.helix.constant.IdentityProvider;
import com.onixbyte.helix.domain.entity.embeddable.UserIdentityId;
import jakarta.persistence.*; // 导入 Jakarta Persistence API
import java.time.LocalDateTime;
import java.util.Objects;
/**
* Represents an external identity mapping for a user.
* <p>
* This entity manages the relationship between internal user accounts and external identity
* providers (such as OAuth providers, LDAP systems, or other authentication services). It enables
* users to authenticate using external credentials while maintaining a consistent internal user
* identity within the Helix system.
*
* @author zihluwang
* @version 1.0
* @since 1.0
*/
@Entity
@Table(name = "user_identities")
public class UserIdentity {
/**
* The composite primary key for the entity, composed of userId, provider, and externalId.
*/
@EmbeddedId
private UserIdentityId id;
/**
* The timestamp when this identity mapping was created.
*/
@Column(nullable = false, updatable = false)
private LocalDateTime createdAt;
/**
* The timestamp when this identity mapping was last updated.
*/
@Column(nullable = false)
private LocalDateTime updatedAt;
// --- JPA Lifecycle Callbacks for Auditing ---
@PrePersist
protected void onCreate() {
if (this.createdAt == null) {
this.createdAt = LocalDateTime.now();
}
if (this.updatedAt == null) {
this.updatedAt = LocalDateTime.now();
}
}
@PreUpdate
protected void onUpdate() {
this.updatedAt = LocalDateTime.now();
}
// --- Getters and Setters (Delegating to EmbeddedId) ---
/**
* Gets the identifier of the internal user account from the composite primary key.
* @return the user ID
*/
public Long getUserId() {
return this.id != null ? this.id.getUserId() : null;
}
/**
* Sets the identifier of the internal user account within the composite primary key.
* @param userId the user ID
*/
public void setUserId(Long userId) {
if (this.id == null) this.id = new UserIdentityId();
this.id.setUserId(userId);
}
/**
* Gets the external identity provider from the composite primary key.
* @return the provider
*/
public IdentityProvider getProvider() {
return this.id != null ? this.id.getProvider() : null;
}
/**
* Sets the external identity provider within the composite primary key.
* @param provider the provider
*/
public void setProvider(IdentityProvider provider) {
if (this.id == null) this.id = new UserIdentityId();
this.id.setProvider(provider);
}
/**
* Gets the unique identifier from the external provider from the composite primary key.
* @return the external ID
*/
public String getExternalId() {
return this.id != null ? this.id.getExternalId() : null;
}
/**
* Sets the unique identifier from the external provider within the composite primary key.
* @param externalId the external ID
*/
public void setExternalId(String externalId) {
if (this.id == null) this.id = new UserIdentityId();
this.id.setExternalId(externalId);
}
public LocalDateTime getCreatedAt() {
return createdAt;
}
public void setCreatedAt(LocalDateTime createdAt) {
this.createdAt = createdAt;
}
public LocalDateTime getUpdatedAt() {
return updatedAt;
}
public void setUpdatedAt(LocalDateTime updatedAt) {
this.updatedAt = updatedAt;
}
// --- Constructors (Adjusted for EmbeddedId) ---
public UserIdentity() {
this.id = new UserIdentityId(); // Initialize ID object for safety
}
public UserIdentity(Long userId, IdentityProvider provider, String externalId, LocalDateTime createdAt, LocalDateTime updatedAt) {
this.id = new UserIdentityId(userId, provider, externalId);
this.createdAt = createdAt;
this.updatedAt = updatedAt;
}
// --- Overrides (Simplified to use the Id object for entity equality) ---
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
UserIdentity that = (UserIdentity) o;
return Objects.equals(id, that.id); // Entity equality based on primary key
}
@Override
public int hashCode() {
return Objects.hash(id); // Hash code based on primary key
}
@Override
public String toString() {
return "UserIdentity{" +
"userId=" + getUserId() +
", provider=" + getProvider() +
", externalId='" + getExternalId() + '\'' +
", createdAt=" + createdAt +
", updatedAt=" + updatedAt +
'}';
}
/**
* Creates a new Builder instance for constructing UserIdentity objects.
*
* @return a new UserIdentityBuilder instance
*/
public static UserIdentityBuilder builder() {
return new UserIdentityBuilder();
}
/**
* Builder class for constructing UserIdentity instances with a fluent API.
* <p>
* This builder provides a convenient way to construct UserIdentity objects with optional parameters,
* following the Builder pattern for improved readability and maintainability.
*/
public static class UserIdentityBuilder {
private Long userId;
private IdentityProvider provider;
private String externalId;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
private UserIdentityBuilder() {
}
public UserIdentityBuilder userId(Long userId) {
this.userId = userId;
return this;
}
public UserIdentityBuilder provider(IdentityProvider provider) {
this.provider = provider;
return this;
}
public UserIdentityBuilder externalId(String externalId) {
this.externalId = externalId;
return this;
}
public UserIdentityBuilder createdAt(LocalDateTime createdAt) {
this.createdAt = createdAt;
return this;
}
public UserIdentityBuilder updatedAt(LocalDateTime updatedAt) {
this.updatedAt = updatedAt;
return this;
}
/**
* Builds and returns a new UserIdentity instance with the configured properties.
*
* @return a new UserIdentity instance
*/
public UserIdentity build() {
return new UserIdentity(userId, provider, externalId, createdAt, updatedAt);
}
}
}
@@ -0,0 +1,176 @@
package com.onixbyte.helix.domain.entity;
import com.onixbyte.helix.domain.entity.embeddable.UserRoleId;
import jakarta.persistence.*;
import java.time.LocalDateTime;
import java.util.Objects;
/**
* Represents the association between users and roles in the access control system.
* <p>
* This entity implements the many-to-many relationship between users and roles, allowing users to
* be assigned multiple roles and roles to be assigned to multiple users. It forms a fundamental
* part of the role-based access control (RBAC) system by defining which roles are assigned to
* each user, thereby determining their permissions and access levels within the system.
*
* @author zihluwang
* @version 1.0
* @since 1.0
*/
@Entity
@Table(name = "user_roles")
public class UserRole {
/**
* The composite primary key of the association, mapped to the 'user_id' and 'role_id' columns.
* <p>
* This field embeds the UserRoleId object which holds the foreign keys to the User and Role tables.
*/
@EmbeddedId
private UserRoleId id;
/**
* The timestamp when this user-role assignment was created.
* <p>
* This field is automatically set upon creation in the database.
*/
@Column(nullable = false, updatable = false)
private LocalDateTime createdAt;
// --- JPA Lifecycle Callbacks for Auditing ---
@PrePersist
protected void onCreate() {
if (this.createdAt == null) {
this.createdAt = LocalDateTime.now();
}
}
// --- Getters and Setters (Delegating to EmbeddedId and fields) ---
/**
* Gets the identifier of the role from the composite primary key.
*
* @return the role's unique identifier
*/
public Long getRoleId() {
return this.id != null ? this.id.getRoleId() : null;
}
/**
* Sets the identifier of the role within the composite primary key.
*
* @param roleId the role's unique identifier
*/
public void setRoleId(Long roleId) {
if (this.id == null) this.id = new UserRoleId();
this.id.setRoleId(roleId);
}
/**
* Gets the identifier of the user from the composite primary key.
*
* @return the user's unique identifier
*/
public Long getUserId() {
return this.id != null ? this.id.getUserId() : null;
}
/**
* Sets the identifier of the user within the composite primary key.
*
* @param userId the user's unique identifier
*/
public void setUserId(Long userId) {
if (this.id == null) this.id = new UserRoleId();
this.id.setUserId(userId);
}
public LocalDateTime getCreatedAt() {
return createdAt;
}
public void setCreatedAt(LocalDateTime createdAt) {
this.createdAt = createdAt;
}
public UserRole() {
this.id = new UserRoleId();
}
public UserRole(Long roleId, Long userId, LocalDateTime createdAt) {
this.id = new UserRoleId(userId, roleId);
this.createdAt = createdAt;
}
// --- Overrides (Simplified to use the Id object for entity equality) ---
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
UserRole userRole = (UserRole) o;
return Objects.equals(id, userRole.id); // Entity equality based on primary key
}
@Override
public int hashCode() {
return Objects.hash(id); // Hash code based on primary key
}
@Override
public String toString() {
return "UserRole{" +
"roleId=" + getRoleId() +
", userId=" + getUserId() +
", createdAt=" + createdAt +
'}';
}
// --- Builder Class (Adjusted to build the Id object) ---
/**
* Creates a new Builder instance for constructing UserRole objects.
*
* @return a new UserRoleBuilder instance
*/
public static UserRoleBuilder builder() {
return new UserRoleBuilder();
}
/**
* Builder class for constructing UserRole instances with a fluent API.
*/
public static class UserRoleBuilder {
private Long roleId;
private Long userId;
private LocalDateTime createdAt;
private UserRoleBuilder() {
}
public UserRoleBuilder roleId(Long roleId) {
this.roleId = roleId;
return this;
}
public UserRoleBuilder userId(Long userId) {
this.userId = userId;
return this;
}
public UserRoleBuilder createdAt(LocalDateTime createdAt) {
this.createdAt = createdAt;
return this;
}
/**
* Builds and returns a new UserRole instance with the configured properties.
*
* @return a new UserRole instance
*/
public UserRole build() {
return new UserRole(roleId, userId, createdAt);
}
}
}
@@ -0,0 +1,70 @@
package com.onixbyte.helix.domain.entity.embeddable;
import jakarta.persistence.Column;
import jakarta.persistence.Embeddable;
import java.io.Serial;
import java.io.Serializable;
import java.util.Objects;
/**
* Represents the composite primary key for the RoleAuthority association entity.
*/
@Embeddable
public class RoleAuthorityId implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* The identifier of the role, mapping to the 'role_id' column.
* <p>
* This field should match the corresponding field name in the RoleAuthority entity
* if the naming is non-standard, but typically matches the column name in the database.
*/
@Column(nullable = false)
private Long roleId;
/**
* The identifier of the authority, mapping to the 'authority_id' column.
*/
@Column(nullable = false)
private Long authorityId;
public RoleAuthorityId() {
}
public RoleAuthorityId(Long roleId, Long authorityId) {
this.roleId = roleId;
this.authorityId = authorityId;
}
public Long getRoleId() {
return roleId;
}
public void setRoleId(Long roleId) {
this.roleId = roleId;
}
public Long getAuthorityId() {
return authorityId;
}
public void setAuthorityId(Long authorityId) {
this.authorityId = authorityId;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
RoleAuthorityId that = (RoleAuthorityId) o;
return Objects.equals(roleId, that.roleId) && Objects.equals(authorityId, that.authorityId);
}
@Override
public int hashCode() {
return Objects.hash(roleId, authorityId);
}
}
@@ -0,0 +1,97 @@
package com.onixbyte.helix.domain.entity.embeddable;
import com.onixbyte.helix.constant.IdentityProvider;
import jakarta.persistence.Column;
import jakarta.persistence.Embeddable;
import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated;
import org.hibernate.annotations.JdbcType;
import org.hibernate.dialect.PostgreSQLEnumJdbcType;
import java.io.Serial;
import java.io.Serializable;
import java.util.Objects;
/**
* Represents the composite primary key for the UserIdentity entity.
* <p>
* This key is composed of the internal user ID, the identity provider, and the external ID
* from that provider.
*/
@Embeddable
public class UserIdentityId implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* The identifier of the internal user account, corresponding to the 'user_id' column.
* <p>
* This also serves as a foreign key reference to the User entity.
*/
@Column(nullable = false)
private Long userId;
/**
* The external identity provider, corresponding to the 'provider' column.
*/
@Column(nullable = false)
@Enumerated
@JdbcType(PostgreSQLEnumJdbcType.class)
private IdentityProvider provider;
/**
* The unique identifier from the external provider, corresponding to the 'external_id' column.
*/
@Column(nullable = false)
private String externalId;
// --- Constructors ---
public UserIdentityId() {
}
public UserIdentityId(Long userId, IdentityProvider provider, String externalId) {
this.userId = userId;
this.provider = provider;
this.externalId = externalId;
}
// --- Getters and Setters (Omitted for brevity, but should exist) ---
public Long getUserId() {
return userId;
}
public void setUserId(Long userId) {
this.userId = userId;
}
public IdentityProvider getProvider() {
return provider;
}
public void setProvider(IdentityProvider provider) {
this.provider = provider;
}
public String getExternalId() {
return externalId;
}
public void setExternalId(String externalId) {
this.externalId = externalId;
}
// --- equals and hashCode (REQUIRED for composite keys) ---
@Override
public boolean equals(Object o) {
if (o == null || getClass() != o.getClass()) return false;
UserIdentityId that = (UserIdentityId) o;
return Objects.equals(userId, that.userId) && provider == that.provider && Objects.equals(externalId, that.externalId);
}
@Override
public int hashCode() {
return Objects.hash(userId, provider, externalId);
}
}
@@ -0,0 +1,72 @@
package com.onixbyte.helix.domain.entity.embeddable;
import jakarta.persistence.Column;
import jakarta.persistence.Embeddable;
import java.io.Serial;
import java.io.Serializable;
import java.util.Objects;
/**
* Represents the composite primary key for the UserRole association entity.
* <p>
* This class combines the userId and roleId to uniquely identify a user's role assignment.
*/
@Embeddable
public class UserRoleId implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* The identifier of the user in this association, mapping to the 'user_id' column.
*/
@Column(nullable = false)
private Long userId;
/**
* The identifier of the role in this association, mapping to the 'role_id' column.
*/
@Column(nullable = false)
private Long roleId;
// --- Constructors ---
public UserRoleId() {
}
public UserRoleId(Long userId, Long roleId) {
this.userId = userId;
this.roleId = roleId;
}
// --- Getters and Setters ---
public Long getUserId() {
return userId;
}
public void setUserId(Long userId) {
this.userId = userId;
}
public Long getRoleId() {
return roleId;
}
public void setRoleId(Long roleId) {
this.roleId = roleId;
}
// --- equals and hashCode (REQUIRED for composite keys) ---
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
UserRoleId that = (UserRoleId) o;
return Objects.equals(userId, that.userId) && Objects.equals(roleId, that.roleId);
}
@Override
public int hashCode() {
return Objects.hash(userId, roleId);
}
}
@@ -0,0 +1,18 @@
package com.onixbyte.helix.domain.model;
import java.util.ArrayList;
import java.util.List;
public record TreeNode<T>(
T item,
List<TreeNode<T>> children
) {
/**
* Helper constructor for building.
* @param item the item
*/
public TreeNode(T item) {
this(item, new ArrayList<>());
}
}
@@ -0,0 +1,264 @@
package com.onixbyte.helix.domain.view;
import com.onixbyte.helix.constant.Status;
import com.onixbyte.helix.domain.entity.Authority;
import java.time.LocalDateTime;
import java.util.Objects;
/**
* View object for Authority entity.
* <p>
* This class represents the view layer abstraction of an Authority, providing a simplified
* representation suitable for presentation in user interfaces and API responses.
*
* @author Zihlu Wang
* @version 1.0.0
* @since 1.0.0
*/
public class AuthorityView {
/**
* The unique identifier for the authority.
*/
private Long id;
/**
* The unique code identifier for the authority.
*/
private String code;
/**
* The human-readable name of the authority.
*/
private String name;
/**
* A detailed description of what this authority grants.
*/
private String description;
/**
* The current status of the authority.
*/
private Status status;
/**
* The timestamp when this authority record was created.
*/
private LocalDateTime createdAt;
/**
* The timestamp when this authority record was last updated.
*/
private LocalDateTime updatedAt;
/**
* Default constructor for serialisation frameworks.
*/
public AuthorityView() {
}
/**
* Constructs a new AuthorityView with all fields specified.
*
* @param id the unique identifier
* @param code the unique code identifier
* @param name the human-readable name
* @param description the detailed description
* @param status the current status
* @param createdAt the creation timestamp
* @param updatedAt the last update timestamp
*/
public AuthorityView(Long id, String code, String name, String description, Status status,
LocalDateTime createdAt, LocalDateTime updatedAt) {
this.id = id;
this.code = code;
this.name = name;
this.description = description;
this.status = status;
this.createdAt = createdAt;
this.updatedAt = updatedAt;
}
/**
* Creates an AuthorityView from an Authority entity.
*
* @param entity the Authority entity
* @return a new AuthorityView instance
*/
public static AuthorityView fromEntity(Authority entity) {
if (entity == null) {
return null;
}
return new AuthorityView(
entity.getId(),
entity.getCode(),
entity.getName(),
entity.getDescription(),
entity.getStatus(),
entity.getCreatedAt(),
entity.getUpdatedAt()
);
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public Status getStatus() {
return status;
}
public void setStatus(Status status) {
this.status = status;
}
public LocalDateTime getCreatedAt() {
return createdAt;
}
public void setCreatedAt(LocalDateTime createdAt) {
this.createdAt = createdAt;
}
public LocalDateTime getUpdatedAt() {
return updatedAt;
}
public void setUpdatedAt(LocalDateTime updatedAt) {
this.updatedAt = updatedAt;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
AuthorityView that = (AuthorityView) o;
return Objects.equals(id, that.id) &&
Objects.equals(code, that.code) &&
Objects.equals(name, that.name) &&
Objects.equals(description, that.description) &&
status == that.status &&
Objects.equals(createdAt, that.createdAt) &&
Objects.equals(updatedAt, that.updatedAt);
}
@Override
public int hashCode() {
return Objects.hash(id, code, name, description, status, createdAt, updatedAt);
}
@Override
public String toString() {
return "AuthorityView{" +
"id=" + id +
", code='" + code + '\'' +
", name='" + name + '\'' +
", description='" + description + '\'' +
", status=" + status +
", createdAt=" + createdAt +
", updatedAt=" + updatedAt +
'}';
}
/**
* Returns a builder for constructing AuthorityView instances.
*
* @return a new AuthorityViewBuilder instance
*/
public static AuthorityViewBuilder builder() {
return new AuthorityViewBuilder();
}
/**
* Builder class for constructing {@link AuthorityView} instances.
* <p>
* This builder provides a fluent interface for creating AuthorityView objects with optional
* parameters, following the builder pattern for improved readability and maintainability.
*/
public static class AuthorityViewBuilder {
private Long id;
private String code;
private String name;
private String description;
private Status status;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
private AuthorityViewBuilder() {
}
public AuthorityViewBuilder id(Long id) {
this.id = id;
return this;
}
public AuthorityViewBuilder code(String code) {
this.code = code;
return this;
}
public AuthorityViewBuilder name(String name) {
this.name = name;
return this;
}
public AuthorityViewBuilder description(String description) {
this.description = description;
return this;
}
public AuthorityViewBuilder status(Status status) {
this.status = status;
return this;
}
public AuthorityViewBuilder createdAt(LocalDateTime createdAt) {
this.createdAt = createdAt;
return this;
}
public AuthorityViewBuilder updatedAt(LocalDateTime updatedAt) {
this.updatedAt = updatedAt;
return this;
}
/**
* Builds and returns a new AuthorityView instance with the configured parameters.
*
* @return a new AuthorityView instance
*/
public AuthorityView build() {
return new AuthorityView(id, code, name, description, status, createdAt, updatedAt);
}
}
}
@@ -0,0 +1,270 @@
package com.onixbyte.helix.domain.view;
import com.onixbyte.helix.constant.Status;
import com.onixbyte.helix.domain.entity.Department;
import java.time.LocalDateTime;
import java.util.Objects;
/**
* View object for Department entity.
* <p>
* This class represents the view layer abstraction of a Department, providing a simplified
* representation suitable for presentation in user interfaces and API responses.
*
* @author Zihlu Wang
* @version 1.0.0
* @since 1.0.0
*/
public class DepartmentView {
/**
* The unique identifier for the department.
*/
private Long id;
/**
* The name of the department.
*/
private String name;
/**
* The identifier of the parent department.
*/
private Long parentId;
/**
* The sort order for displaying departments.
*/
private Integer sort;
/**
* The current status of the department.
*/
private Status status;
/**
* The timestamp when this department record was created.
*/
private LocalDateTime createdAt;
/**
* The timestamp when this department record was last updated.
*/
private LocalDateTime updatedAt;
/**
* Default constructor for serialisation frameworks.
*/
public DepartmentView() {
}
/**
* Constructs a new DepartmentView with all fields specified.
*
* @param id the unique identifier
* @param name the name of the department
* @param parentId the identifier of the parent department
* @param sort the sort order for displaying departments
* @param status the current status
* @param createdAt the creation timestamp
* @param updatedAt the last update timestamp
*/
public DepartmentView(Long id, String name, Long parentId, Integer sort,
Status status, LocalDateTime createdAt, LocalDateTime updatedAt) {
this.id = id;
this.name = name;
this.parentId = parentId;
this.sort = sort;
this.status = status;
this.createdAt = createdAt;
this.updatedAt = updatedAt;
}
/**
* Creates a DepartmentView from a Department entity.
*
* @param entity the Department entity
* @return a new DepartmentView instance
*/
public static DepartmentView fromEntity(Department entity) {
if (entity == null) {
return null;
}
return new DepartmentView(
entity.getId(),
entity.getName(),
entity.getParentId(),
entity.getSort(),
entity.getStatus(),
entity.getCreatedAt(),
entity.getUpdatedAt()
);
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Long getParentId() {
return parentId;
}
public void setParentId(Long parentId) {
this.parentId = parentId;
}
public Integer getSort() {
return sort;
}
public void setSort(Integer sort) {
this.sort = sort;
}
public Status getStatus() {
return status;
}
public void setStatus(Status status) {
this.status = status;
}
public LocalDateTime getCreatedAt() {
return createdAt;
}
public void setCreatedAt(LocalDateTime createdAt) {
this.createdAt = createdAt;
}
public LocalDateTime getUpdatedAt() {
return updatedAt;
}
public void setUpdatedAt(LocalDateTime updatedAt) {
this.updatedAt = updatedAt;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
DepartmentView that = (DepartmentView) o;
return Objects.equals(id, that.id) &&
Objects.equals(name, that.name) &&
Objects.equals(parentId, that.parentId) &&
Objects.equals(sort, that.sort) &&
status == that.status &&
Objects.equals(createdAt, that.createdAt) &&
Objects.equals(updatedAt, that.updatedAt);
}
@Override
public int hashCode() {
return Objects.hash(id, name, parentId, sort, status, createdAt, updatedAt);
}
@Override
public String toString() {
return "DepartmentView{" +
"id=" + id +
", name='" + name + '\'' +
", parentId=" + parentId +
", sort=" + sort +
", status=" + status +
", createdAt=" + createdAt +
", updatedAt=" + updatedAt +
'}';
}
/**
* Returns a builder for constructing DepartmentView instances.
*
* @return a new DepartmentViewBuilder instance
*/
public static DepartmentViewBuilder builder() {
return new DepartmentViewBuilder();
}
/**
* Builder class for constructing {@link DepartmentView} instances.
* <p>
* This builder provides a fluent interface for creating DepartmentView objects with optional
* parameters, following the builder pattern for improved readability and maintainability.
*/
public static class DepartmentViewBuilder {
private Long id;
private String name;
private Long parentId;
private String treePath;
private Integer sort;
private Status status;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
private DepartmentViewBuilder() {
}
public DepartmentViewBuilder id(Long id) {
this.id = id;
return this;
}
public DepartmentViewBuilder name(String name) {
this.name = name;
return this;
}
public DepartmentViewBuilder parentId(Long parentId) {
this.parentId = parentId;
return this;
}
public DepartmentViewBuilder treePath(String treePath) {
this.treePath = treePath;
return this;
}
public DepartmentViewBuilder sort(Integer sort) {
this.sort = sort;
return this;
}
public DepartmentViewBuilder status(Status status) {
this.status = status;
return this;
}
public DepartmentViewBuilder createdAt(LocalDateTime createdAt) {
this.createdAt = createdAt;
return this;
}
public DepartmentViewBuilder updatedAt(LocalDateTime updatedAt) {
this.updatedAt = updatedAt;
return this;
}
/**
* Builds and returns a new DepartmentView instance with the configured parameters.
*
* @return a new DepartmentView instance
*/
public DepartmentView build() {
return new DepartmentView(id, name, parentId, sort, status, createdAt, updatedAt);
}
}
}
@@ -0,0 +1,271 @@
package com.onixbyte.helix.domain.view;
import com.onixbyte.helix.constant.Status;
import com.onixbyte.helix.domain.entity.Position;
import java.time.LocalDateTime;
import java.util.Objects;
/**
* View object for Position entity.
* <p>
* This class represents a view of the Position entity, providing a data transfer object
* for position information in the organisational structure.
*
* @author zihluwang
* @version 1.0
* @since 1.0
*/
public class PositionView {
/**
* The unique identifier for the position.
*/
private Long id;
/**
* The human-readable name of the position.
*/
private String name;
/**
* The unique code identifier for the position.
*/
private String code;
/**
* A detailed description of the position's responsibilities and requirements.
*/
private String description;
/**
* The sort order for displaying positions.
*/
private Integer sort;
/**
* The current status of the position.
*/
private Status status;
/**
* The timestamp when this position record was created.
*/
private LocalDateTime createdAt;
/**
* The timestamp when this position record was last updated.
*/
private LocalDateTime updatedAt;
/**
* Default constructor.
*/
public PositionView() {
}
/**
* Constructor with all fields.
*/
public PositionView(Long id, String name, String code, String description, Integer sort,
Status status, LocalDateTime createdAt, LocalDateTime updatedAt) {
this.id = id;
this.name = name;
this.code = code;
this.description = description;
this.sort = sort;
this.status = status;
this.createdAt = createdAt;
this.updatedAt = updatedAt;
}
/**
* Creates a PositionView from a Position entity.
*
* @param position the Position entity
* @return the PositionView object
*/
public static PositionView fromEntity(Position position) {
if (position == null) {
return null;
}
return new PositionView(
position.getId(),
position.getName(),
position.getCode(),
position.getDescription(),
position.getSort(),
position.getStatus(),
position.getCreatedAt(),
position.getUpdatedAt()
);
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public Integer getSort() {
return sort;
}
public void setSort(Integer sort) {
this.sort = sort;
}
public Status getStatus() {
return status;
}
public void setStatus(Status status) {
this.status = status;
}
public LocalDateTime getCreatedAt() {
return createdAt;
}
public void setCreatedAt(LocalDateTime createdAt) {
this.createdAt = createdAt;
}
public LocalDateTime getUpdatedAt() {
return updatedAt;
}
public void setUpdatedAt(LocalDateTime updatedAt) {
this.updatedAt = updatedAt;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
PositionView that = (PositionView) o;
return Objects.equals(id, that.id) &&
Objects.equals(name, that.name) &&
Objects.equals(code, that.code) &&
Objects.equals(description, that.description) &&
Objects.equals(sort, that.sort) &&
status == that.status &&
Objects.equals(createdAt, that.createdAt) &&
Objects.equals(updatedAt, that.updatedAt);
}
@Override
public int hashCode() {
return Objects.hash(id, name, code, description, sort, status, createdAt, updatedAt);
}
@Override
public String toString() {
return "PositionView{" +
"id=" + id +
", name='" + name + '\'' +
", code='" + code + '\'' +
", description='" + description + '\'' +
", sort=" + sort +
", status=" + status +
", createdAt=" + createdAt +
", updatedAt=" + updatedAt +
'}';
}
/**
* Creates a new builder instance.
*
* @return a new PositionViewBuilder
*/
public static PositionViewBuilder builder() {
return new PositionViewBuilder();
}
/**
* Builder class for PositionView.
*/
public static class PositionViewBuilder {
private Long id;
private String name;
private String code;
private String description;
private Integer sort;
private Status status;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
private PositionViewBuilder() {
}
public PositionViewBuilder id(Long id) {
this.id = id;
return this;
}
public PositionViewBuilder name(String name) {
this.name = name;
return this;
}
public PositionViewBuilder code(String code) {
this.code = code;
return this;
}
public PositionViewBuilder description(String description) {
this.description = description;
return this;
}
public PositionViewBuilder sort(Integer sort) {
this.sort = sort;
return this;
}
public PositionViewBuilder status(Status status) {
this.status = status;
return this;
}
public PositionViewBuilder createdAt(LocalDateTime createdAt) {
this.createdAt = createdAt;
return this;
}
public PositionViewBuilder updatedAt(LocalDateTime updatedAt) {
this.updatedAt = updatedAt;
return this;
}
public PositionView build() {
return new PositionView(id, name, code, description, sort, status, createdAt, updatedAt);
}
}
}
@@ -0,0 +1,154 @@
package com.onixbyte.helix.domain.view;
import com.onixbyte.helix.domain.entity.RoleAuthority;
import java.time.LocalDateTime;
import java.util.Objects;
/**
* View object for RoleAuthority entity.
* <p>
* This class represents a view of the RoleAuthority entity, providing a data transfer object
* for role-authority association information in the access control system.
*
* @author zihluwang
* @version 1.0
* @since 1.0
*/
public class RoleAuthorityView {
/**
* The identifier of the role in this association.
*/
private Long roleId;
/**
* The identifier of the authority in this association.
*/
private Long authorityId;
/**
* The timestamp when this role-authority association was created.
*/
private LocalDateTime createdAt;
/**
* Default constructor.
*/
public RoleAuthorityView() {
}
/**
* Constructor with all fields.
*/
public RoleAuthorityView(Long roleId, Long authorityId, LocalDateTime createdAt) {
this.roleId = roleId;
this.authorityId = authorityId;
this.createdAt = createdAt;
}
/**
* Creates a RoleAuthorityView from a RoleAuthority entity.
*
* @param roleAuthority the RoleAuthority entity
* @return the RoleAuthorityView object
*/
public static RoleAuthorityView fromEntity(RoleAuthority roleAuthority) {
if (roleAuthority == null) {
return null;
}
return new RoleAuthorityView(
roleAuthority.getRoleId(),
roleAuthority.getAuthorityId(),
roleAuthority.getCreatedAt()
);
}
public Long getRoleId() {
return roleId;
}
public void setRoleId(Long roleId) {
this.roleId = roleId;
}
public Long getAuthorityId() {
return authorityId;
}
public void setAuthorityId(Long authorityId) {
this.authorityId = authorityId;
}
public LocalDateTime getCreatedAt() {
return createdAt;
}
public void setCreatedAt(LocalDateTime createdAt) {
this.createdAt = createdAt;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
RoleAuthorityView that = (RoleAuthorityView) o;
return Objects.equals(roleId, that.roleId) &&
Objects.equals(authorityId, that.authorityId) &&
Objects.equals(createdAt, that.createdAt);
}
@Override
public int hashCode() {
return Objects.hash(roleId, authorityId, createdAt);
}
@Override
public String toString() {
return "RoleAuthorityView{" +
"roleId=" + roleId +
", authorityId=" + authorityId +
", createdAt=" + createdAt +
'}';
}
/**
* Creates a new builder instance.
*
* @return a new RoleAuthorityViewBuilder
*/
public static RoleAuthorityViewBuilder builder() {
return new RoleAuthorityViewBuilder();
}
/**
* Builder class for RoleAuthorityView.
*/
public static class RoleAuthorityViewBuilder {
private Long roleId;
private Long authorityId;
private LocalDateTime createdAt;
private RoleAuthorityViewBuilder() {
}
public RoleAuthorityViewBuilder roleId(Long roleId) {
this.roleId = roleId;
return this;
}
public RoleAuthorityViewBuilder authorityId(Long authorityId) {
this.authorityId = authorityId;
return this;
}
public RoleAuthorityViewBuilder createdAt(LocalDateTime createdAt) {
this.createdAt = createdAt;
return this;
}
public RoleAuthorityView build() {
return new RoleAuthorityView(roleId, authorityId, createdAt);
}
}
}
@@ -0,0 +1,294 @@
package com.onixbyte.helix.domain.view;
import com.onixbyte.helix.constant.Status;
import com.onixbyte.helix.domain.entity.Role;
import java.time.LocalDateTime;
import java.util.Objects;
/**
* View object for Role entity.
* <p>
* This class represents a view of the Role entity, providing a data transfer object
* for role information within the access control system.
*
* @author zihluwang
* @version 1.0
* @since 1.0
*/
public class RoleView {
/**
* The unique identifier for the role.
*/
private Long id;
/**
* The human-readable name of the role.
*/
private String name;
/**
* The unique code identifier for the role.
*/
private String code;
/**
* The sort order for displaying roles.
*/
private Integer sort;
/**
* Indicates whether this role is assigned by default to new users.
*/
private Boolean defaultValue;
/**
* A detailed description of the role's purpose and permissions.
*/
private String description;
/**
* The current status of the role.
*/
private Status status;
/**
* The timestamp when this role record was created.
*/
private LocalDateTime createdAt;
/**
* The timestamp when this role record was last updated.
*/
private LocalDateTime updatedAt;
/**
* Default constructor.
*/
public RoleView() {
}
/**
* Constructor with all fields.
*/
public RoleView(Long id, String name, String code, Integer sort, Boolean defaultValue,
String description, Status status, LocalDateTime createdAt, LocalDateTime updatedAt) {
this.id = id;
this.name = name;
this.code = code;
this.sort = sort;
this.defaultValue = defaultValue;
this.description = description;
this.status = status;
this.createdAt = createdAt;
this.updatedAt = updatedAt;
}
/**
* Creates a RoleView from a Role entity.
*
* @param role the Role entity
* @return the RoleView object
*/
public static RoleView fromEntity(Role role) {
if (role == null) {
return null;
}
return new RoleView(
role.getId(),
role.getName(),
role.getCode(),
role.getSort(),
role.getDefaultValue(),
role.getDescription(),
role.getStatus(),
role.getCreatedAt(),
role.getUpdatedAt()
);
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public Integer getSort() {
return sort;
}
public void setSort(Integer sort) {
this.sort = sort;
}
public Boolean getDefaultValue() {
return defaultValue;
}
public void setDefaultValue(Boolean defaultValue) {
this.defaultValue = defaultValue;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public Status getStatus() {
return status;
}
public void setStatus(Status status) {
this.status = status;
}
public LocalDateTime getCreatedAt() {
return createdAt;
}
public void setCreatedAt(LocalDateTime createdAt) {
this.createdAt = createdAt;
}
public LocalDateTime getUpdatedAt() {
return updatedAt;
}
public void setUpdatedAt(LocalDateTime updatedAt) {
this.updatedAt = updatedAt;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
RoleView roleView = (RoleView) o;
return Objects.equals(id, roleView.id) &&
Objects.equals(name, roleView.name) &&
Objects.equals(code, roleView.code) &&
Objects.equals(sort, roleView.sort) &&
Objects.equals(defaultValue, roleView.defaultValue) &&
Objects.equals(description, roleView.description) &&
status == roleView.status &&
Objects.equals(createdAt, roleView.createdAt) &&
Objects.equals(updatedAt, roleView.updatedAt);
}
@Override
public int hashCode() {
return Objects.hash(id, name, code, sort, defaultValue, description, status, createdAt, updatedAt);
}
@Override
public String toString() {
return "RoleView{" +
"id=" + id +
", name='" + name + '\'' +
", code='" + code + '\'' +
", sort=" + sort +
", defaultValue=" + defaultValue +
", description='" + description + '\'' +
", status=" + status +
", createdAt=" + createdAt +
", updatedAt=" + updatedAt +
'}';
}
/**
* Creates a new builder instance.
*
* @return a new RoleViewBuilder
*/
public static RoleViewBuilder builder() {
return new RoleViewBuilder();
}
/**
* Builder class for RoleView.
*/
public static class RoleViewBuilder {
private Long id;
private String name;
private String code;
private Integer sort;
private Boolean defaultValue;
private String description;
private Status status;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
private RoleViewBuilder() {
}
public RoleViewBuilder id(Long id) {
this.id = id;
return this;
}
public RoleViewBuilder name(String name) {
this.name = name;
return this;
}
public RoleViewBuilder code(String code) {
this.code = code;
return this;
}
public RoleViewBuilder sort(Integer sort) {
this.sort = sort;
return this;
}
public RoleViewBuilder defaultValue(Boolean defaultValue) {
this.defaultValue = defaultValue;
return this;
}
public RoleViewBuilder description(String description) {
this.description = description;
return this;
}
public RoleViewBuilder status(Status status) {
this.status = status;
return this;
}
public RoleViewBuilder createdAt(LocalDateTime createdAt) {
this.createdAt = createdAt;
return this;
}
public RoleViewBuilder updatedAt(LocalDateTime updatedAt) {
this.updatedAt = updatedAt;
return this;
}
public RoleView build() {
return new RoleView(id, name, code, sort, defaultValue, description, status, createdAt, updatedAt);
}
}
}
@@ -0,0 +1,202 @@
package com.onixbyte.helix.domain.view;
import com.onixbyte.helix.constant.IdentityProvider;
import com.onixbyte.helix.domain.entity.UserIdentity;
import java.time.LocalDateTime;
import java.util.Objects;
/**
* View object for UserIdentity entity.
* <p>
* This class represents a view of the UserIdentity entity, providing a data transfer object
* for external identity mapping information in the Helix system.
*
* @author zihluwang
* @version 1.0
* @since 1.0
*/
public class UserIdentityView {
/**
* The identifier of the internal user account.
*/
private Long userId;
/**
* The external identity provider.
*/
private IdentityProvider provider;
/**
* The unique identifier from the external provider.
*/
private String externalId;
/**
* The timestamp when this identity mapping was created.
*/
private LocalDateTime createdAt;
/**
* The timestamp when this identity mapping was last updated.
*/
private LocalDateTime updatedAt;
/**
* Default constructor.
*/
public UserIdentityView() {
}
/**
* Constructor with all fields.
*/
public UserIdentityView(Long userId, IdentityProvider provider, String externalId,
LocalDateTime createdAt, LocalDateTime updatedAt) {
this.userId = userId;
this.provider = provider;
this.externalId = externalId;
this.createdAt = createdAt;
this.updatedAt = updatedAt;
}
/**
* Creates a UserIdentityView from a UserIdentity entity.
*
* @param userIdentity the UserIdentity entity
* @return the UserIdentityView object
*/
public static UserIdentityView fromEntity(UserIdentity userIdentity) {
if (userIdentity == null) {
return null;
}
return new UserIdentityView(
userIdentity.getUserId(),
userIdentity.getProvider(),
userIdentity.getExternalId(),
userIdentity.getCreatedAt(),
userIdentity.getUpdatedAt()
);
}
public Long getUserId() {
return userId;
}
public void setUserId(Long userId) {
this.userId = userId;
}
public IdentityProvider getProvider() {
return provider;
}
public void setProvider(IdentityProvider provider) {
this.provider = provider;
}
public String getExternalId() {
return externalId;
}
public void setExternalId(String externalId) {
this.externalId = externalId;
}
public LocalDateTime getCreatedAt() {
return createdAt;
}
public void setCreatedAt(LocalDateTime createdAt) {
this.createdAt = createdAt;
}
public LocalDateTime getUpdatedAt() {
return updatedAt;
}
public void setUpdatedAt(LocalDateTime updatedAt) {
this.updatedAt = updatedAt;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
UserIdentityView that = (UserIdentityView) o;
return Objects.equals(userId, that.userId) &&
provider == that.provider &&
Objects.equals(externalId, that.externalId) &&
Objects.equals(createdAt, that.createdAt) &&
Objects.equals(updatedAt, that.updatedAt);
}
@Override
public int hashCode() {
return Objects.hash(userId, provider, externalId, createdAt, updatedAt);
}
@Override
public String toString() {
return "UserIdentityView{" +
"userId=" + userId +
", provider=" + provider +
", externalId='" + externalId + '\'' +
", createdAt=" + createdAt +
", updatedAt=" + updatedAt +
'}';
}
/**
* Creates a new builder instance.
*
* @return a new UserIdentityViewBuilder
*/
public static UserIdentityViewBuilder builder() {
return new UserIdentityViewBuilder();
}
/**
* Builder class for UserIdentityView.
*/
public static class UserIdentityViewBuilder {
private Long userId;
private IdentityProvider provider;
private String externalId;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
private UserIdentityViewBuilder() {
}
public UserIdentityViewBuilder userId(Long userId) {
this.userId = userId;
return this;
}
public UserIdentityViewBuilder provider(IdentityProvider provider) {
this.provider = provider;
return this;
}
public UserIdentityViewBuilder externalId(String externalId) {
this.externalId = externalId;
return this;
}
public UserIdentityViewBuilder createdAt(LocalDateTime createdAt) {
this.createdAt = createdAt;
return this;
}
public UserIdentityViewBuilder updatedAt(LocalDateTime updatedAt) {
this.updatedAt = updatedAt;
return this;
}
public UserIdentityView build() {
return new UserIdentityView(userId, provider, externalId, createdAt, updatedAt);
}
}
}
@@ -0,0 +1,154 @@
package com.onixbyte.helix.domain.view;
import com.onixbyte.helix.domain.entity.UserRole;
import java.time.LocalDateTime;
import java.util.Objects;
/**
* View object for UserRole entity.
* <p>
* This class represents a view of the UserRole entity, providing a data transfer object
* for user-role association information in the access control system.
*
* @author zihluwang
* @version 1.0
* @since 1.0
*/
public class UserRoleView {
/**
* The identifier of the role in this association.
*/
private Long roleId;
/**
* The identifier of the user in this association.
*/
private Long userId;
/**
* The timestamp when this user-role assignment was created.
*/
private LocalDateTime createdAt;
/**
* Default constructor.
*/
public UserRoleView() {
}
/**
* Constructor with all fields.
*/
public UserRoleView(Long roleId, Long userId, LocalDateTime createdAt) {
this.roleId = roleId;
this.userId = userId;
this.createdAt = createdAt;
}
/**
* Creates a UserRoleView from a UserRole entity.
*
* @param userRole the UserRole entity
* @return the UserRoleView object
*/
public static UserRoleView fromEntity(UserRole userRole) {
if (userRole == null) {
return null;
}
return new UserRoleView(
userRole.getRoleId(),
userRole.getUserId(),
userRole.getCreatedAt()
);
}
public Long getRoleId() {
return roleId;
}
public void setRoleId(Long roleId) {
this.roleId = roleId;
}
public Long getUserId() {
return userId;
}
public void setUserId(Long userId) {
this.userId = userId;
}
public LocalDateTime getCreatedAt() {
return createdAt;
}
public void setCreatedAt(LocalDateTime createdAt) {
this.createdAt = createdAt;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
UserRoleView that = (UserRoleView) o;
return Objects.equals(roleId, that.roleId) &&
Objects.equals(userId, that.userId) &&
Objects.equals(createdAt, that.createdAt);
}
@Override
public int hashCode() {
return Objects.hash(roleId, userId, createdAt);
}
@Override
public String toString() {
return "UserRoleView{" +
"roleId=" + roleId +
", userId=" + userId +
", createdAt=" + createdAt +
'}';
}
/**
* Creates a new builder instance.
*
* @return a new UserRoleViewBuilder
*/
public static UserRoleViewBuilder builder() {
return new UserRoleViewBuilder();
}
/**
* Builder class for UserRoleView.
*/
public static class UserRoleViewBuilder {
private Long roleId;
private Long userId;
private LocalDateTime createdAt;
private UserRoleViewBuilder() {
}
public UserRoleViewBuilder roleId(Long roleId) {
this.roleId = roleId;
return this;
}
public UserRoleViewBuilder userId(Long userId) {
this.userId = userId;
return this;
}
public UserRoleViewBuilder createdAt(LocalDateTime createdAt) {
this.createdAt = createdAt;
return this;
}
public UserRoleView build() {
return new UserRoleView(roleId, userId, createdAt);
}
}
}
@@ -0,0 +1,366 @@
package com.onixbyte.helix.domain.view;
import com.onixbyte.helix.constant.UserStatus;
import com.onixbyte.helix.domain.entity.User;
import java.time.LocalDateTime;
import java.util.Objects;
/**
* View object for User entity.
* <p>
* This class represents a view of the User entity, providing a data transfer object
* for user information in the Helix system.
*
* @author zihluwang
* @version 1.0
* @since 1.0
*/
public class UserView {
/**
* The unique identifier for the user.
*/
private Long id;
/**
* The unique username for authentication purposes.
*/
private String username;
/**
* The user's complete full name.
*/
private String fullName;
/**
* The user's email address.
*/
private String email;
/**
* The country code for the user's phone number.
*/
private String regionAbbreviation;
/**
* The user's phone number without the country code.
*/
private String phoneNumber;
/**
* The URL to the user's avatar image.
*/
private String avatarUrl;
/**
* The current status of the user account.
*/
private UserStatus status;
/**
* The identifier of the department to which this user belongs.
*/
private Long departmentId;
/**
* The identifier of the position held by this user.
*/
private Long positionId;
/**
* The timestamp when this user record was created.
*/
private LocalDateTime createdAt;
/**
* The timestamp when this user record was last updated.
*/
private LocalDateTime updatedAt;
/**
* Default constructor.
*/
public UserView() {
}
/**
* Constructor with all fields (excluding password for security).
*/
public UserView(Long id, String username, String fullName, String email, String regionAbbreviation,
String phoneNumber, String avatarUrl, UserStatus status, Long departmentId,
Long positionId, LocalDateTime createdAt, LocalDateTime updatedAt) {
this.id = id;
this.username = username;
this.fullName = fullName;
this.email = email;
this.regionAbbreviation = regionAbbreviation;
this.phoneNumber = phoneNumber;
this.avatarUrl = avatarUrl;
this.status = status;
this.departmentId = departmentId;
this.positionId = positionId;
this.createdAt = createdAt;
this.updatedAt = updatedAt;
}
/**
* Creates a UserView from a User entity (excluding password for security).
*
* @param user the User entity
* @return the UserView object
*/
public static UserView fromEntity(User user) {
if (user == null) {
return null;
}
return new UserView(
user.getId(),
user.getUsername(),
user.getFullName(),
user.getEmail(),
user.getRegionAbbreviation(),
user.getPhoneNumber(),
user.getAvatarUrl(),
user.getStatus(),
user.getDepartmentId(),
user.getPositionId(),
user.getCreatedAt(),
user.getUpdatedAt()
);
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getFullName() {
return fullName;
}
public void setFullName(String fullName) {
this.fullName = fullName;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getRegionAbbreviation() {
return regionAbbreviation;
}
public void setRegionAbbreviation(String regionAbbreviation) {
this.regionAbbreviation = regionAbbreviation;
}
public String getPhoneNumber() {
return phoneNumber;
}
public void setPhoneNumber(String phoneNumber) {
this.phoneNumber = phoneNumber;
}
public String getAvatarUrl() {
return avatarUrl;
}
public void setAvatarUrl(String avatarUrl) {
this.avatarUrl = avatarUrl;
}
public UserStatus getStatus() {
return status;
}
public void setStatus(UserStatus status) {
this.status = status;
}
public Long getDepartmentId() {
return departmentId;
}
public void setDepartmentId(Long departmentId) {
this.departmentId = departmentId;
}
public Long getPositionId() {
return positionId;
}
public void setPositionId(Long positionId) {
this.positionId = positionId;
}
public LocalDateTime getCreatedAt() {
return createdAt;
}
public void setCreatedAt(LocalDateTime createdAt) {
this.createdAt = createdAt;
}
public LocalDateTime getUpdatedAt() {
return updatedAt;
}
public void setUpdatedAt(LocalDateTime updatedAt) {
this.updatedAt = updatedAt;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
UserView userView = (UserView) o;
return Objects.equals(id, userView.id) &&
Objects.equals(username, userView.username) &&
Objects.equals(fullName, userView.fullName) &&
Objects.equals(email, userView.email) &&
Objects.equals(regionAbbreviation, userView.regionAbbreviation) &&
Objects.equals(phoneNumber, userView.phoneNumber) &&
Objects.equals(avatarUrl, userView.avatarUrl) &&
status == userView.status &&
Objects.equals(departmentId, userView.departmentId) &&
Objects.equals(positionId, userView.positionId) &&
Objects.equals(createdAt, userView.createdAt) &&
Objects.equals(updatedAt, userView.updatedAt);
}
@Override
public int hashCode() {
return Objects.hash(id, username, fullName, email, regionAbbreviation, phoneNumber, avatarUrl,
status, departmentId, positionId, createdAt, updatedAt);
}
@Override
public String toString() {
return "UserView{" +
"id=" + id +
", username='" + username + '\'' +
", fullName='" + fullName + '\'' +
", email='" + email + '\'' +
", regionAbbreviation='" + regionAbbreviation + '\'' +
", phoneNumber='" + phoneNumber + '\'' +
", avatarUrl='" + avatarUrl + '\'' +
", status=" + status +
", departmentId=" + departmentId +
", positionId=" + positionId +
", createdAt=" + createdAt +
", updatedAt=" + updatedAt +
'}';
}
/**
* Creates a new builder instance.
*
* @return a new UserViewBuilder
*/
public static UserViewBuilder builder() {
return new UserViewBuilder();
}
/**
* Builder class for UserView.
*/
public static class UserViewBuilder {
private Long id;
private String username;
private String fullName;
private String email;
private String regionAbbreviation;
private String phoneNumber;
private String avatarUrl;
private UserStatus status;
private Long departmentId;
private Long positionId;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
private UserViewBuilder() {
}
public UserViewBuilder id(Long id) {
this.id = id;
return this;
}
public UserViewBuilder username(String username) {
this.username = username;
return this;
}
public UserViewBuilder fullName(String fullName) {
this.fullName = fullName;
return this;
}
public UserViewBuilder email(String email) {
this.email = email;
return this;
}
public UserViewBuilder regionAbbreviation(String regionAbbreviation) {
this.regionAbbreviation = regionAbbreviation;
return this;
}
public UserViewBuilder phoneNumber(String phoneNumber) {
this.phoneNumber = phoneNumber;
return this;
}
public UserViewBuilder avatarUrl(String avatarUrl) {
this.avatarUrl = avatarUrl;
return this;
}
public UserViewBuilder status(UserStatus status) {
this.status = status;
return this;
}
public UserViewBuilder departmentId(Long departmentId) {
this.departmentId = departmentId;
return this;
}
public UserViewBuilder positionId(Long positionId) {
this.positionId = positionId;
return this;
}
public UserViewBuilder createdAt(LocalDateTime createdAt) {
this.createdAt = createdAt;
return this;
}
public UserViewBuilder updatedAt(LocalDateTime updatedAt) {
this.updatedAt = updatedAt;
return this;
}
public UserView build() {
return new UserView(id, username, fullName, email, regionAbbreviation, phoneNumber, avatarUrl,
status, departmentId, positionId, createdAt, updatedAt);
}
}
}
@@ -0,0 +1,24 @@
package com.onixbyte.helix.domain.web.request;
import com.onixbyte.helix.constant.UserStatus;
import jakarta.validation.constraints.NotBlank;
import java.util.List;
public record AddUserRequest(
@NotBlank(message = "Username cannot be empty.")
String username,
@NotBlank(message = "Password cannot be empty.")
String password,
@NotBlank(message = "Full name cannot be empty.")
String fullName,
String email,
String regionAbbreviation,
String phoneNumber,
String avatarUrl,
UserStatus status,
Long departmentId,
Long positionId,
List<Long> roleIds
) {
}
@@ -0,0 +1,13 @@
package com.onixbyte.helix.domain.web.request;
import jakarta.validation.constraints.Pattern;
public record QueryRoleRequest(
String name,
String code,
@Pattern(
regexp = "^(ACTIVE|INACTIVE)?$",
message = "状态仅可以是 ACTIVE、INACTIVE 其中之一")
String status
) {
}
@@ -0,0 +1,19 @@
package com.onixbyte.helix.domain.web.request;
import jakarta.validation.constraints.Pattern;
import java.time.LocalDateTime;
public record QueryUserRequest(
Long departmentId,
String username,
String regionAbbreviation,
String phoneNumber,
@Pattern(
regexp = "^(ACTIVE|INACTIVE|LOCKED)?$",
message = "状态仅可以是 ACTIVE、INACTIVE 或 LOCKED 其中之一")
String status,
LocalDateTime createdAtStart,
LocalDateTime createdAtEnd
) {
}
@@ -0,0 +1,10 @@
package com.onixbyte.helix.domain.web.request;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
public record ResetPasswordRequest(
@NotNull(message = "用户 ID 不能为空") Long id,
@NotBlank(message = "密码不能为空") String password
) {
}
@@ -0,0 +1,22 @@
package com.onixbyte.helix.domain.web.request;
import com.onixbyte.helix.constant.UserStatus;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Positive;
import java.util.List;
public record UpdateUserRequest(
@NotNull(message = "User ID cannot be null")
@Positive(message = "User ID must be positive")
Long id,
String fullName,
String email,
String regionAbbreviation,
String phoneNumber,
String avatarUrl,
UserStatus status,
Long departmentId,
Long positionId
) {
}
@@ -0,0 +1,9 @@
package com.onixbyte.helix.domain.web.request;
public record UsernamePasswordLoginRequest(
String username,
String password,
String uuid,
String captcha
) {
}
@@ -0,0 +1,38 @@
package com.onixbyte.helix.domain.web.response;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* Record representing a standardised business exception response.
* <p>
* This record encapsulates the essential information returned to clients when business logic
* exceptions occur within the Helix application. It provides a consistent structure for
* error communication, ensuring that all business exception responses follow the same format and
* contain the necessary information for client-side error handling.
* <p>
* The response includes a timestamp indicating when the exception occurred and a human-readable
* message explaining the nature of the error. This standardised format enables consistent error
* handling across different client applications and API consumers.
* <p>
* As a record, this class is immutable and provides automatic implementations of {@code equals()},
* {@code hashCode()}, and {@code toString()} methods, making it suitable for use in functional
* programming patterns and ensuring thread safety in concurrent environments.
* <p>
* This response entity is typically used by exception handlers and error processing components to
* communicate business rule violations, validation failures, and other application-specific errors
* to API clients.
*
* @param timestamp the timestamp when the exception occurred, providing temporal context for error
* tracking and debugging
* @param message a human-readable explanation of the exception, suitable for display to end users
* or logging purposes
* @author zihluwang
* @see Serializable
* @since 1.0.0
*/
public record BizExceptionResponse(
LocalDateTime timestamp,
String message
) implements Serializable {
}
@@ -0,0 +1,7 @@
package com.onixbyte.helix.domain.web.response;
public record CaptchaResponse(
String captcha,
String uuid
) {
}
@@ -0,0 +1,9 @@
package com.onixbyte.helix.domain.web.response;
public record FileUploadResponse(
String originalFileName,
String contentType,
Long size,
String url
) {
}
@@ -0,0 +1,13 @@
package com.onixbyte.helix.domain.web.response;
import com.onixbyte.helix.domain.entity.User;
public record LoginSuccessResponse(
String accessToken,
User user
) {
public LoginSuccessResponse {
user.setPassword(null);
}
}
@@ -0,0 +1,252 @@
package com.onixbyte.helix.domain.web.response;
import com.onixbyte.helix.constant.UserStatus;
import java.time.LocalDateTime;
public class UserDetailResponse {
private String id;
private String username;
private String fullName;
private String email;
private String regionAbbreviation;
private String phoneNumber;
private String avatarUrl;
private UserStatus status;
private Long departmentId;
private String departmentName;
private Long positionId;
private String positionName;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
public UserDetailResponse() {
}
public UserDetailResponse(String id, String username, String fullName, String email, String regionAbbreviation, String phoneNumber, String avatarUrl, UserStatus status, Long departmentId, String departmentName, Long positionId, String positionName, LocalDateTime createdAt, LocalDateTime updatedAt) {
this.id = id;
this.username = username;
this.fullName = fullName;
this.email = email;
this.regionAbbreviation = regionAbbreviation;
this.phoneNumber = phoneNumber;
this.avatarUrl = avatarUrl;
this.status = status;
this.departmentId = departmentId;
this.departmentName = departmentName;
this.positionId = positionId;
this.positionName = positionName;
this.createdAt = createdAt;
this.updatedAt = updatedAt;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getFullName() {
return fullName;
}
public void setFullName(String fullName) {
this.fullName = fullName;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getRegionAbbreviation() {
return regionAbbreviation;
}
public void setRegionAbbreviation(String regionAbbreviation) {
this.regionAbbreviation = regionAbbreviation;
}
public String getPhoneNumber() {
return phoneNumber;
}
public void setPhoneNumber(String phoneNumber) {
this.phoneNumber = phoneNumber;
}
public String getAvatarUrl() {
return avatarUrl;
}
public void setAvatarUrl(String avatarUrl) {
this.avatarUrl = avatarUrl;
}
public UserStatus getStatus() {
return status;
}
public void setStatus(UserStatus status) {
this.status = status;
}
public Long getDepartmentId() {
return departmentId;
}
public void setDepartmentId(Long departmentId) {
this.departmentId = departmentId;
}
public String getDepartmentName() {
return departmentName;
}
public void setDepartmentName(String departmentName) {
this.departmentName = departmentName;
}
public Long getPositionId() {
return positionId;
}
public void setPositionId(Long positionId) {
this.positionId = positionId;
}
public String getPositionName() {
return positionName;
}
public void setPositionName(String positionName) {
this.positionName = positionName;
}
public LocalDateTime getCreatedAt() {
return createdAt;
}
public void setCreatedAt(LocalDateTime createdAt) {
this.createdAt = createdAt;
}
public LocalDateTime getUpdatedAt() {
return updatedAt;
}
public void setUpdatedAt(LocalDateTime updatedAt) {
this.updatedAt = updatedAt;
}
public static UserDetailResponseBuilder builder() {
return new UserDetailResponseBuilder();
}
public static class UserDetailResponseBuilder {
private String id;
private String username;
private String fullName;
private String email;
private String regionAbbreviation;
private String phoneNumber;
private String avatarUrl;
private UserStatus status;
private Long departmentId;
private String departmentName;
private Long positionId;
private String positionName;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
private UserDetailResponseBuilder() {
}
public UserDetailResponseBuilder id(String id) {
this.id = id;
return this;
}
public UserDetailResponseBuilder username(String username) {
this.username = username;
return this;
}
public UserDetailResponseBuilder fullName(String fullName) {
this.fullName = fullName;
return this;
}
public UserDetailResponseBuilder email(String email) {
this.email = email;
return this;
}
public UserDetailResponseBuilder regionAbbreviation(String regionAbbreviation) {
this.regionAbbreviation = regionAbbreviation;
return this;
}
public UserDetailResponseBuilder phoneNumber(String phoneNumber) {
this.phoneNumber = phoneNumber;
return this;
}
public UserDetailResponseBuilder avatarUrl(String avatarUrl) {
this.avatarUrl = avatarUrl;
return this;
}
public UserDetailResponseBuilder status(UserStatus status) {
this.status = status;
return this;
}
public UserDetailResponseBuilder departmentId(Long departmentId) {
this.departmentId = departmentId;
return this;
}
public UserDetailResponseBuilder departmentName(String departmentName) {
this.departmentName = departmentName;
return this;
}
public UserDetailResponseBuilder positionId(Long positionId) {
this.positionId = positionId;
return this;
}
public UserDetailResponseBuilder positionName(String positionName) {
this.positionName = positionName;
return this;
}
public UserDetailResponseBuilder createdAt(LocalDateTime createdAt) {
this.createdAt = createdAt;
return this;
}
public UserDetailResponseBuilder updatedAt(LocalDateTime updatedAt) {
this.updatedAt = updatedAt;
return this;
}
public UserDetailResponse build() {
return new UserDetailResponse(id, username, fullName, email, regionAbbreviation, phoneNumber, avatarUrl, status, departmentId, departmentName, positionId, positionName, createdAt, updatedAt);
}
}
}
@@ -0,0 +1,74 @@
package com.onixbyte.helix.exception;
import org.springframework.http.HttpStatus;
/**
* Custom runtime exception for business logic violations and application-specific errors.
* <p>
* This exception is designed to handle business rule violations, validation failures, and other
* application-specific error conditions that occur during normal operation. Unlike
* system exceptions, business exceptions are expected and should be handled gracefully by the
* application's error handling mechanisms.
* <p>
* Each business exception carries an HTTP status code that indicates the appropriate response
* status to return to clients when the exception occurs. This enables consistent error handling
* across REST API endpoints and provides meaningful HTTP responses to API consumers.
* <p>
* Common use cases include:
* <ul>
* <li>Resource not found scenarios (404 Not Found)</li>
* <li>Validation failures (400 Bad Request)</li>
* <li>Authorisation violations (403 Forbidden)</li>
* <li>Business rule violations (422 Unprocessable Entity)</li>
* <li>Conflict situations (409 Conflict)</li>
* </ul>
* <p>
* The exception integrates seamlessly with Spring Boot's exception handling framework and can be
* caught by global exception handlers to produce standardised error responses.
*
* @author zihluwang
* @see RuntimeException
* @see org.springframework.http.HttpStatus
* @since 1.0.0
*/
public class BizException extends RuntimeException {
/**
* The HTTP status code associated with this business exception.
* <p>
* This status code indicates the appropriate HTTP response status that should be returned to
* clients when this exception occurs. It enables consistent error handling across
* REST API endpoints.
*/
private final HttpStatus status;
/**
* Constructs a new business exception with the specified HTTP status and message.
*
* @param message the detailed error message explaining the business logic violation
*/
public BizException(String message) {
super(message);
this.status = HttpStatus.INTERNAL_SERVER_ERROR;
}
/**
* Constructs a new business exception with the specified HTTP status and message.
*
* @param status the HTTP status code to associate with this exception
* @param message the detailed error message explaining the business logic violation
*/
public BizException(HttpStatus status, String message) {
super(message);
this.status = status;
}
/**
* Returns the HTTP status code associated with this business exception.
*
* @return the HTTP status code that should be used in the error response
*/
public HttpStatus getStatus() {
return status;
}
}
@@ -0,0 +1,43 @@
/**
* Extension and plugin support package for the Helix application.
* <p>
* This package is designed to contain extension points, plugin interfaces,
* and extensibility mechanisms that allow the Helix application to be
* extended with additional functionality without modifying core components.
* <p>
* <strong>Intended Contents:</strong>
* <ul>
* <li><strong>Extension Interfaces:</strong> Define contracts for pluggable components</li>
* <li><strong>Plugin Loaders:</strong> Mechanisms for discovering and loading extensions</li>
* <li>
* <strong>Extension Points:</strong> Well-defined points where custom functionality can
* be injected
* </li>
* <li><strong>Custom Annotations:</strong> Annotations for marking and configuring extensions</li>
* </ul>
* <p>
* <strong>Design Principles:</strong>
* <ul>
* <li>
* <strong>Loose Coupling:</strong> Extensions should be loosely coupled to core functionality
* </li>
* <li>
* <strong>Service Provider Interface (SPI):</strong> Use SPI patterns for plugin discovery
* </li>
* <li>
* <strong>Configuration-Driven:</strong> Allow extensions to be configured through
* application properties
* </li>
* <li>
* <strong>Lifecycle Management:</strong> Provide proper initialisation and cleanup for extensions
* </li>
* </ul>
* <p>
* This package follows the extensibility patterns commonly used in enterprise applications to
* support customisation and third-party integrations whilst maintaining system stability
* and performance.
*
* @author zihluwang
* @since 1.0.0
*/
package com.onixbyte.helix.extension;
@@ -0,0 +1,21 @@
package com.onixbyte.helix.extension.redis.serializer;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
public class JacksonSerialiser {
public static final GenericJackson2JsonRedisSerializer INSTANCE = initialiseSerializer();
private static GenericJackson2JsonRedisSerializer initialiseSerializer() {
var serializer = new GenericJackson2JsonRedisSerializer();
serializer.configure((configurer) -> {
configurer.registerModule(new JavaTimeModule());
configurer.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
});
return serializer;
}
}
@@ -0,0 +1,81 @@
package com.onixbyte.helix.filter;
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.onixbyte.helix.manager.AuthorityManager;
import com.onixbyte.helix.manager.UserManager;
import com.onixbyte.helix.security.authentication.UsernamePasswordAuthentication;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.jspecify.annotations.NonNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import java.io.IOException;
import java.util.Objects;
@Component
public class TokenAuthenticationFilter extends OncePerRequestFilter {
private final static Logger log = LoggerFactory.getLogger(TokenAuthenticationFilter.class);
private final Algorithm algorithm;
private final UserManager userManager;
private final AuthorityManager authorityManager;
public TokenAuthenticationFilter(Algorithm algorithm, UserManager userManager, AuthorityManager authorityManager) {
this.algorithm = algorithm;
this.userManager = userManager;
this.authorityManager = authorityManager;
}
@Override
protected void doFilterInternal(
@NonNull HttpServletRequest request,
@NonNull HttpServletResponse response,
@NonNull FilterChain filterChain
) throws ServletException, IOException {
var token = request.getHeader("Authorization");
if (Objects.isNull(token) || token.isBlank()) {
filterChain.doFilter(request, response);
return;
}
if (!token.startsWith("Bearer ")) {
filterChain.doFilter(request, response);
return;
}
token = token.substring(7);
var verifier = JWT.require(algorithm)
.withIssuer("Helix Server")
.build();
try {
var decodedToken = verifier.verify(token);
var username = decodedToken.getSubject();
var user = userManager.selectByUsername(username);
var authorities = authorityManager.queryByUserId(user.getId())
.stream()
.map((authority) -> (GrantedAuthority) authority::getCode)
.toList();
user.setPassword(null);
var authentication = UsernamePasswordAuthentication.authenticated(user, authorities);
SecurityContextHolder.getContext().setAuthentication(authentication);
filterChain.doFilter(request, response);
} catch (JWTVerificationException e) {
log.error("JWT verification failed.", e);
filterChain.doFilter(request, response);
}
}
}
@@ -0,0 +1,18 @@
package com.onixbyte.helix.manager;
import com.onixbyte.helix.properties.ApplicationProperties;
import org.springframework.stereotype.Component;
@Component
public class ApplicationManager {
private final ApplicationProperties applicationProperties;
public ApplicationManager(ApplicationProperties applicationProperties) {
this.applicationProperties = applicationProperties;
}
public String getDefaultEmail() {
return applicationProperties.defaultEmail();
}
}
@@ -0,0 +1,40 @@
package com.onixbyte.helix.manager;
import com.onixbyte.helix.constant.CacheName;
import com.onixbyte.helix.domain.entity.Asset;
import com.onixbyte.helix.exception.BizException;
import com.onixbyte.helix.mapper.AssetMapper;
import com.onixbyte.helix.repository.AssetRepository;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
@Component
public class AssetManager {
private final AssetMapper assetMapper;
private final AssetRepository assetRepository;
public AssetManager(AssetMapper assetMapper, AssetRepository assetRepository) {
this.assetMapper = assetMapper;
this.assetRepository = assetRepository;
}
@CachePut(cacheNames = CacheName.ASSET, key = "#result.id", unless = "#result == null")
public Asset save(Asset asset) {
return assetRepository.save(asset);
}
@Cacheable(cacheNames = CacheName.ASSET, key = "#assetId")
public Asset queryByAssetId(Long assetId) {
return assetRepository.findById(assetId)
.orElse(null);
}
@CacheEvict(cacheNames = CacheName.ASSET, key = "#assetId")
public void deleteById(Long assetId) {
assetRepository.deleteById(assetId);
}
}
@@ -0,0 +1,24 @@
package com.onixbyte.helix.manager;
import com.onixbyte.helix.constant.CacheName;
import com.onixbyte.helix.domain.entity.Authority;
import com.onixbyte.helix.mapper.AuthorityMapper;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Component;
import java.util.List;
@Component
public class AuthorityManager {
private final AuthorityMapper authorityMapper;
public AuthorityManager(AuthorityMapper authorityMapper) {
this.authorityMapper = authorityMapper;
}
@Cacheable(cacheNames = CacheName.AUTHORITIES_OF_USER, key = "#userId")
public List<Authority> queryByUserId(Long userId) {
return authorityMapper.selectByUserId(userId);
}
}
@@ -0,0 +1,39 @@
package com.onixbyte.helix.manager;
import com.onixbyte.helix.client.RedisClient;
import com.onixbyte.helix.constant.CacheName;
import com.onixbyte.helix.properties.CaptchaProperties;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.Cache;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import java.time.Duration;
import java.util.Objects;
@Component
public class CaptchaManager {
private final RedisClient redisClient;
@Autowired
public CaptchaManager(RedisClient redisClient) {
this.redisClient = redisClient;
}
public void setCaptcha(String uuid, String captchaCode) {
redisClient.set(buildCacheKey(uuid), captchaCode, Duration.ofMinutes(5L));
}
public String getCaptcha(String uuid) {
var key = buildCacheKey(uuid);
var captcha = redisClient.get(key, String.class);
redisClient.delete(key);
return captcha;
}
private String buildCacheKey(String uuid) {
return "captcha::" + uuid;
}
}
@@ -0,0 +1,26 @@
package com.onixbyte.helix.manager;
import com.onixbyte.helix.domain.entity.Department;
import com.onixbyte.helix.mapper.DepartmentMapper;
import com.onixbyte.helix.repository.DepartmentRepository;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Component;
import java.util.*;
@Component
public class DepartmentManager {
private final DepartmentMapper departmentMapper;
private final DepartmentRepository departmentRepository;
public DepartmentManager(DepartmentMapper departmentMapper, DepartmentRepository departmentRepository) {
this.departmentMapper = departmentMapper;
this.departmentRepository = departmentRepository;
}
public Page<Department> selectAll(Pageable pageable) {
return departmentRepository.findAll(pageable);
}
}
@@ -0,0 +1,23 @@
package com.onixbyte.helix.manager;
import com.onixbyte.helix.domain.entity.Menu;
import com.onixbyte.helix.mapper.MenuMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import java.util.List;
@Component
public class MenuManager {
private static final Logger log = LoggerFactory.getLogger(MenuManager.class);
private final MenuMapper menuMapper;
public MenuManager(MenuMapper menuMapper) {
this.menuMapper = menuMapper;
}
public List<Menu> selectActiveMenusByUserId(Long userId) {
return menuMapper.selectActiveMenusByUserId(userId);
}
}
@@ -0,0 +1,25 @@
package com.onixbyte.helix.manager;
import com.onixbyte.helix.domain.entity.Position;
import com.onixbyte.helix.mapper.PositionMapper;
import com.onixbyte.helix.repository.PositionRepository;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Component;
@Component
public class PositionManager {
private final PositionMapper positionMapper;
private final PositionRepository positionRepository;
public PositionManager(PositionMapper positionMapper, PositionRepository positionRepository) {
this.positionMapper = positionMapper;
this.positionRepository = positionRepository;
}
public Page<Position> selectAll(Pageable pageable) {
return positionRepository.findAll(pageable);
}
}
@@ -0,0 +1,45 @@
package com.onixbyte.helix.manager;
import com.onixbyte.helix.domain.database.query.wrapper.QueryRoleWrapper;
import com.onixbyte.helix.domain.entity.Role;
import com.onixbyte.helix.exception.BizException;
import com.onixbyte.helix.mapper.RoleMapper;
import com.onixbyte.helix.repository.RoleRepository;
import org.springframework.data.domain.Example;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Optional;
@Component
public class RoleManager {
private final RoleMapper roleMapper;
private final RoleRepository roleRepository;
public RoleManager(RoleMapper roleMapper, RoleRepository roleRepository) {
this.roleMapper = roleMapper;
this.roleRepository = roleRepository;
}
public void validateRoles(List<Long> roleIds) {
if (!roleMapper.areRolesExisted(roleIds)) {
throw new BizException(HttpStatus.BAD_REQUEST, "Role does not exist in database.");
}
}
public Optional<Role> getRole(Role example) {
return roleRepository.findOne(Example.of(example));
}
public Page<Role> selectAll(Pageable pageable, QueryRoleWrapper wrapper) {
var records = roleMapper.selectAll(pageable, wrapper);
var total = roleMapper.count(wrapper);
return new PageImpl<>(records, pageable, total);
}
}
@@ -0,0 +1,21 @@
package com.onixbyte.helix.manager;
import com.onixbyte.helix.domain.entity.Setting;
import com.onixbyte.helix.repository.SettingRepository;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Component;
@Component
public class SettingManager {
private final SettingRepository settingRepository;
public SettingManager(SettingRepository settingRepository) {
this.settingRepository = settingRepository;
}
@Cacheable(cacheNames = "setting", key = "#name")
public Setting getSettingByName(String name) {
return settingRepository.getSettingByName(name);
}
}
@@ -0,0 +1,123 @@
package com.onixbyte.helix.manager;
import com.onixbyte.helix.common.regex.Patterns;
import com.onixbyte.helix.constant.CacheName;
import com.onixbyte.helix.domain.database.query.wrapper.QueryUserWrapper;
import com.onixbyte.helix.domain.entity.User;
import com.onixbyte.helix.domain.web.response.UserDetailResponse;
import com.onixbyte.helix.exception.BizException;
import com.onixbyte.helix.mapper.UserMapper;
import com.onixbyte.helix.repository.UserRepository;
import com.onixbyte.region.Region;
import org.apache.commons.lang3.StringUtils;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.domain.Example;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.http.HttpStatus;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import java.util.Optional;
@Component
public class UserManager {
private final UserMapper userMapper;
private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder;
public UserManager(UserMapper userMapper, UserRepository userRepository, PasswordEncoder passwordEncoder) {
this.userMapper = userMapper;
this.userRepository = userRepository;
this.passwordEncoder = passwordEncoder;
}
/**
* Get user by username, and cache this user by username.
*
* @param username username
* @return user
*/
@Cacheable(cacheNames = CacheName.USER, key = "#username", unless = "#result == null")
public User selectByUsername(String username) {
return userRepository.findOne(Example.of(User.builder()
.username(username)
.build()))
.orElse(null);
}
/**
* Query paginated users.
*
* @param pageable page request
* @return page result
*/
public Page<UserDetailResponse> selectUserDetailsPage(Pageable pageable, QueryUserWrapper wrapper) {
var result = userMapper.selectListWithDetails(pageable, wrapper);
var total = userMapper.count(wrapper);
return new PageImpl<>(result, pageable, total);
}
@CachePut(cacheNames = CacheName.USER, key = "#user.username")
public User save(User user) {
return userRepository.save(user);
}
public UserDetailResponse queryUserDetailByUserId(Long userId) {
return userMapper.selectWithDetailByUserId(userId);
}
public void deleteById(Long userId) {
userRepository.deleteById(userId);
}
@CachePut(cacheNames = CacheName.USER, key = "#result.username", unless = "#result == null")
@Transactional(rollbackFor = Throwable.class)
public User updateUser(User user) {
var userToUpdate = userRepository.findById(user.getId())
.orElseThrow(() -> new BizException(HttpStatus.BAD_REQUEST, "找不到 ID 为" + user.getId() + "的用户信息"));
Optional.ofNullable(user.getPassword())
.filter(StringUtils::isNotBlank)
.map(passwordEncoder::encode)
.ifPresent(userToUpdate::setPassword);
Optional.ofNullable(user.getFullName())
.filter(StringUtils::isNotBlank)
.ifPresent(userToUpdate::setFullName);
Optional.ofNullable(user.getEmail())
.filter(StringUtils::isNotBlank)
.filter((email) -> Patterns.EMAIL.asPredicate().test(email))
.ifPresent(userToUpdate::setEmail);
Optional.ofNullable(user.getRegionAbbreviation())
.filter(StringUtils::isNotBlank)
.filter(Region::isValidAbbreviation)
.ifPresent(userToUpdate::setRegionAbbreviation);
Optional.ofNullable(user.getPhoneNumber())
.filter(StringUtils::isNotBlank)
.ifPresent(userToUpdate::setPhoneNumber);
Optional.ofNullable(user.getAvatarUrl())
.filter(StringUtils::isNotBlank)
.filter((avatarUrl) -> Patterns.IMAGE_URL.asPredicate().test(avatarUrl) || Patterns.GRAVATAR_IMAGE_URL.asPredicate().test(avatarUrl))
.ifPresent(userToUpdate::setAvatarUrl);
Optional.ofNullable(user.getStatus())
.ifPresent(userToUpdate::setStatus);
Optional.ofNullable(user.getDepartmentId())
.ifPresent(userToUpdate::setDepartmentId);
Optional.ofNullable(user.getPositionId())
.ifPresent(userToUpdate::setPositionId);
return userToUpdate;
}
}
@@ -0,0 +1,34 @@
package com.onixbyte.helix.manager;
import com.onixbyte.helix.domain.entity.UserRole;
import com.onixbyte.helix.exception.BizException;
import com.onixbyte.helix.mapper.UserRoleMapper;
import com.onixbyte.helix.repository.UserRoleRepository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import java.util.List;
@Component
public class UserRoleManager {
private static final Logger log = LoggerFactory.getLogger(UserRoleManager.class);
private final UserRoleMapper userRoleMapper;
private final UserRoleRepository userRoleRepository;
public UserRoleManager(UserRoleMapper userRoleMapper, UserRoleRepository userRoleRepository) {
this.userRoleMapper = userRoleMapper;
this.userRoleRepository = userRoleRepository;
}
public List<UserRole> saveBatch(List<UserRole> userRoles) {
return userRoleRepository.saveAll(userRoles);
}
public void deleteByUserId(Long userId) {
var affectedRows = userRoleRepository.deleteByUserId(userId);
log.info("用户 {} 的角色关联被全部移除(共 {} 条)。", userId, affectedRows);
}
}
@@ -0,0 +1,60 @@
/**
* Business logic management and orchestration package for the Helix application.
* <p>
* This package is designed to contain manager classes that orchestrate complex business operations,
* coordinate between multiple services, and handle cross-cutting concerns that span multiple
* domain boundaries.
* <p>
* <strong>Manager Pattern:</strong> Managers in this package serve as facades or coordinators that
* encapsulate complex business workflows, typically involving multiple services, repositories,
* or external systems. They provide a higher-level abstraction over individual service components.
* <p>
* <strong>Intended Contents:</strong>
* <ul>
* <li>
* <strong>Workflow Managers:</strong> Orchestrate multi-step business processes
* </li>
* <li>
* <strong>Integration Managers:</strong> Coordinate interactions with external systems
* </li>
* <li>
* <strong>Transaction Managers:</strong> Handle complex transactional scenarios
* </li>
* <li>
* <strong>Cache Managers:</strong> Manage caching strategies and cache invalidation
* </li>
* <li>
* <strong>Event Managers:</strong> Coordinate event publishing and handling
* </li>
* </ul>
* <p>
* <strong>Design Guidelines:</strong>
* <ul>
* <li>
* <strong>Single Responsibility:</strong> Each manager should focus on a specific business domain
* or cross-cutting concern
* </li>
* <li>
* <strong>Service Coordination:</strong> Managers should delegate to services rather than
* implementing business logic directly
* </li>
* <li>
* <strong>Transaction Boundaries:</strong> Define clear transactional boundaries for
* complex operations
* </li>
* <li>
* <strong>Error Handling:</strong> Provide comprehensive error handling and rollback mechanisms
* for complex workflows
* </li>
* </ul>
* <p>
* Managers typically sit between the controller layer and the service layer, providing a
* coordination point for complex operations that require multiple service interactions or
* cross-cutting concerns.
*
* @author zihluwang
* @since 1.0.0
* @see com.onixbyte.helix.service
* @see org.springframework.transaction.annotation.Transactional
*/
package com.onixbyte.helix.manager;
@@ -0,0 +1,12 @@
package com.onixbyte.helix.mapper;
import org.apache.ibatis.annotations.Mapper;
/**
* Asset related database operations.
*
* @author zihluwang
*/
@Mapper
public interface AssetMapper {
}
@@ -0,0 +1,19 @@
package com.onixbyte.helix.mapper;
import com.onixbyte.helix.domain.entity.Authority;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
@Mapper
public interface AuthorityMapper {
/**
* Select authorities that is granted to the specific user.
*
* @param userId user ID
* @return authorities
*/
List<Authority> selectByUserId(@Param("userId") Long userId);
}

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