chore: restructure into monorepo and add open-source documentation

- Rename helix-server -> server and helix-web -> web
- Remove helix-api (bruno collection and docs site)
- Merge per-module .gitignore files into the root .gitignore
- Add README, AGENTS, CONTRIBUTING and MIT LICENCE
- Add full-stack docker-compose (postgres, redis, server, web via Caddy)
  with web Dockerfile, Caddyfile and Compose server config
- Keep the Gradle wrapper jar tracked after the .gitignore merge
- Copy gradle.properties into the server Docker build image
This commit is contained in:
siujamo committed 2026-09-01 10:48:45 +08:00
1 parent 112d0f3be4
commit 965b92b47d
315 files changed
+741 -7394

No files matched your search

+26
View File
@@ -0,0 +1,26 @@
# Docker Compose configuration for Helix.
# Copy to `.env` and adjust: cp .env.example .env
# PostgreSQL
PG_DATABASE=helix
PG_USER=helix
PG_PASSWORD=helix
# Redis (keep empty for a local no-password Redis)
REDIS_PASSWORD=
# JWT signing secret — change this in any non-local deployment
TOKEN_SECRET=please-change-this-secret
# Microsoft Entra ID single sign-on (optional for local runs)
MSAL_CLIENT_ID=
MSAL_TENANT_ID=
# S3-compatible object storage (optional — requires enabling `app.asset.enabled`
# in docker/application-prod.yml and providing the credentials below)
S3_ACCESS_KEY_ID=
S3_SECRET_ACCESS_KEY=
# Host ports
SERVER_PORT=8080
WEB_PORT=80
+166
View File
@@ -1,2 +1,168 @@
### 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
### 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/
### Gradle
.gradle
**/build/
!**/src/**/build/
# Keep the Gradle wrapper (checked in per Gradle convention) tracked at any depth
!**/gradle/
!**/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
### Frontend
# Node libraries
node_modules/
# Config
.env.local
.env.*.local
# Claude Code local tooling
.claude/
### Docker Compose
# Root .env holds Compose secrets; use .env.example as a template
/.env
+90
View File
@@ -0,0 +1,90 @@
# AGENTS.md
Guidance for AI coding agents and contributors working in this repository.
It complements [README.md](README.md); read that first for the project overview.
## Repository overview
Helix is a monorepo containing a backend API and a frontend SPA:
- **`server/`** — Spring Boot 3.5 REST API. Java 21, Gradle, Spring Security,
Spring Data JPA + MyBatis, Flyway, PostgreSQL, Redis, Auth0 java-jwt,
AWS SDK (S3). Package root: `com.onixbyte.helix`.
- **`web/`** — React 19 SPA. TypeScript, Vite 7, Ant Design 6, Tailwind CSS 4,
Redux Toolkit, React Router 7, Axios, MSAL.
## Build and test
Server (requires JDK 21):
```bash
cd server
./gradlew build # compiles and runs the JUnit 5 test suite
./gradlew bootRun # starts the API on port 8080
```
Web (requires Node 22+ and pnpm):
```bash
cd web
pnpm install
pnpm dev # Vite dev server
pnpm build # tsc -b && vite build
pnpm lint # ESLint
```
## Server conventions
- **Layering.** Follow the strict call chain
`Controller → Service → Manager → Repository` (MyBatis Mappers sit alongside
JPA repositories for query-heavy reads). Controllers stay thin: parse and
validate input, delegate to a service, return a response DTO. Business rules
live in services; transactional use-case orchestration lives in managers.
- **Validation.** Use the custom validation framework in
`com.onixbyte.helix.validation` (`annotation` / `validator` / `executor` /
`group`) instead of ad-hoc checks where possible.
- **Errors and i18n.** Throw `BizException(HttpStatus, MessageName.X, args...)`.
Message keys are constants declared in `com.onixbyte.helix.shared.MessageName`
and resolved from the bundles in `src/main/resources/i18n`
(`messages.properties`, `messages_en_GB.properties`, `messages_zh_CN.properties`).
Always add a new key to **all** bundles, in the default language and in Chinese.
- **Authentication.** Spring Security with a JWT stored in a cookie.
`TokenAuthenticationFilter` (under `com.onixbyte.helix.filter`) validates the
token; the `X-Authorisation` header carries the exchanged token. Sign-in
providers (Entra ID/MSAL, WeCom) live under `service/auth`. MFA is supported.
- **Persistence.** The schema is owned by Flyway — add migrations under
`src/main/resources/db/migrations/`; `ddl-auto` is disabled. MyBatis XML
mappers live in `src/main/resources/mapper/`. JPA entities go in
`com.onixbyte.helix.domain.entity`.
- **Shared utilities.** Prefer the Onixbyte libraries already on the classpath
(`identity-generator`, `tuple`, `common-toolbox`, `math-toolbox`) over
reimplementing them.
- **Style.** UTF-8 source, Javadoc on public APIs, constructor injection with
`final` fields, builder/record patterns for DTOs. Match the surrounding code.
- **Configuration.** Runtime config is externalised via environment variables
(reference: `config/application-prod.yml.example`). Never hardcode secrets.
Local `config/*.yml` files are gitignored.
## Web conventions
- **State.** Redux Toolkit slices in `src/store` (e.g. `auth-slice`);
consume via `react-redux` hooks.
- **API calls.** Typed per-domain clients in `src/api` (`auth`, `user`, `role`,
`department`, `position`, `menu`). They use the shared Axios instance in
`src/client/web-client/index.ts`, whose `baseURL` comes from
`VITE_API_BASE_URL` and which sends credentials (`withCredentials`).
- **Types.** Request/response types live in `src/types`.
- **Routing.** `react-router` 7 routes are declared in `src/router/index.tsx`.
- **UI.** Ant Design 6 components styled with Tailwind utility classes.
- **Environment.** `VITE_*` variables (see `.env.example`). The Vite dev server
has no proxy — for local development point `VITE_API_BASE_URL` at the backend
directly (e.g. `http://localhost:8080`).
## General guidelines
- **Commits.** Use Conventional Commits (`feat:`, `fix:`, `refactor:`, `chore:`,
`docs:`, `perf:`, `style:`, `test:`). Match the language of the surrounding
history (the log is a mix of English and Chinese).
- **Secrets.** Never commit `.env` files, local config, or credentials.
`web/.dockerignore` excludes local env files from the build context — keep it
that way.
+1
View File
@@ -0,0 +1 @@
@AGENTS.md
+70
View File
@@ -0,0 +1,70 @@
# Contributing to Helix
Thanks for your interest in contributing! Helix is open source under the
[MIT licence](LICENCE). This guide explains how to set up your environment,
make changes and get them merged.
## Getting started
1. Fork the repository and clone your fork.
2. Install the prerequisites:
- Backend: JDK 21 and the Gradle wrapper (bundled in `server/`)
- Frontend: Node.js 22+ and [pnpm](https://pnpm.io/)
- Optional: Docker + Docker Compose for the full-stack run
3. Follow the [Quick start](README.md#quick-start) in the README to run the app
locally.
## Development workflow
- Create a feature branch from `main`, using a short prefix for clarity:
`feat/`, `fix/`, `refactor/`, `chore/` or `docs/` (e.g. `feat/role-audit-log`).
- Keep pull requests small and focused on a single concern.
- Write or update tests for the code you touch.
- Open a pull request against `main` and describe what changed and why.
## Code style
The full conventions are documented in [AGENTS.md](AGENTS.md). The essentials:
**Backend (`server/`)**
- Follow the `Controller → Service → Manager → Repository` layering.
- Use the custom validation framework for input validation.
- Throw `BizException` with a `MessageName` constant and add the message key to
every bundle under `src/main/resources/i18n`.
- Add Flyway migrations for schema changes under `src/main/resources/db/migrations/`.
- Run `./gradlew build` before opening a PR.
**Frontend (`web/`)**
- Use ESLint and Prettier formatting; run `pnpm lint`.
- Keep API clients in `src/api` and state in Redux Toolkit slices.
- Never commit local `.env` files.
## Commit messages
Use [Conventional Commits](https://www.conventionalcommits.org/):
```
feat: support TOTP multi-factor authentication
fix: resolve department name uniqueness check
refactor: extract build cookie
docs: update deployment guide
chore(deps): upgrade spring boot to 3.5.4
```
## Reporting issues
When opening an issue, include:
- What you expected to happen and what actually happened
- Steps to reproduce
- Environment details (OS, Java/Node versions, deployment mode)
Security issues: please report them privately to the maintainers rather than
opening a public issue.
## License
By contributing, you agree that your contributions are licensed under the
[MIT licence](LICENCE).
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2024-2026 OnixByte
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+156
View File
@@ -0,0 +1,156 @@
# Helix
Helix is a self-hosted identity and access management (IAM) and organisation
administration platform built by [OnixByte](https://onixbyte.com). It manages
users, roles, departments, positions and navigation menus behind a single
sign-on experience, with support for Microsoft Entra ID (Azure AD), WeCom
(WeChat Work) and multi-factor authentication.
The project is organised as a monorepo:
- **`server/`** — Spring Boot 3 REST API (Java 21, PostgreSQL, Redis, Flyway)
- **`web/`** — React 19 single-page application (Vite, Ant Design, Tailwind CSS, Redux Toolkit)
- **`docker/`** — deployment support files used by Docker Compose
## Features
- **Authentication & single sign-on** — username/password login with captcha,
Microsoft Entra ID (MSAL) sign-in, WeCom login and MFA
- **User management** — create, edit and deactivate users; assign roles, departments and positions
- **Role-based access control** — roles with fine-grained authorities, enforced across all endpoints
- **Organisation management** — departments, positions and custom navigation menus
- **Asset storage** — optional S3-compatible object storage for uploaded files
- **Internationalisation** — English (`en-GB`) and Chinese (`zh-CN`) message bundles
- **Responsive admin UI** — Ant Design + Tailwind CSS front end
## Tech stack
| Layer | Technologies |
| ----------- | ------------ |
| Backend | Java 21 · Spring Boot 3.5 · Spring Security · Spring Data JPA · MyBatis · Flyway · PostgreSQL · Redis · Auth0 java-jwt · AWS SDK for Java (S3) |
| Frontend | React 19 · TypeScript · Vite 7 · Ant Design 6 · Tailwind CSS 4 · Redux Toolkit · React Router 7 · Axios · MSAL |
| Infra | Docker · Docker Compose · Caddy |
## Repository layout
```
.
├── server/ # Spring Boot REST API
│ ├── src/main/java/com/onixbyte/helix/
│ │ ├── controller/ # HTTP entry points
│ │ ├── service/ # business logic
│ │ ├── manager/ # use-case orchestration
│ │ ├── repository/ # JPA repositories
│ │ ├── mapper/ # MyBatis mappers
│ │ ├── validation/ # custom validation framework
│ │ └── security/ # JWT filter, security configuration
│ ├── src/main/resources/
│ │ ├── db/migrations/ # Flyway SQL migrations
│ │ ├── i18n/ # message bundles (en-GB, zh-CN)
│ │ └── mapper/ # MyBatis XML mappers
│ ├── config/ # runtime config (see application-prod.yml.example)
│ └── Dockerfile
├── web/ # React SPA
│ ├── src/api/ # typed API clients (auth, user, role, …)
│ ├── src/page/ # page components
│ ├── src/store/ # Redux slices
│ ├── src/router/ # react-router routes
│ ├── Dockerfile # builds the SPA and serves it with Caddy
│ └── Caddyfile # static files + /api reverse proxy
├── docker/ # Compose support files
│ └── application-prod.yml # server config mounted into the container
├── docker-compose.yaml # full-stack: postgres + redis + server + web
└── .env.example # Compose environment template
```
## Quick start
### Full stack with Docker Compose
Requires [Docker](https://www.docker.com/) with the Compose plugin.
```bash
cp .env.example .env # optional: adjust ports, secrets and integrations
docker compose up -d --build
```
Once the containers are healthy:
| Service | URL |
| ------------------ | ---------------------------------- |
| Web UI | <http://localhost> |
| REST API | <http://localhost:8080> |
| Health check | <http://localhost:8080/actuator/health> |
Login works out of the box with username/password + captcha. Microsoft Entra ID
and WeCom sign-in require configuring their credentials (see below).
### Manual development
#### Server (JDK 21)
```bash
cd server
cp config/application-prod.yml.example config/application-prod.yml # edit to match your environment
SPRING_PROFILES_ACTIVE=prod ./gradlew bootRun
```
The API starts on <http://localhost:8080>.
#### Web (Node 22+ with pnpm)
```bash
cd web
pnpm install
cp .env.example .env.development.local # point the API base URL at your dev server
pnpm dev
```
Set `VITE_API_BASE_URL=http://localhost:8080` in `.env.development.local` to call
the locally running API directly. The Vite dev server listens on
<http://localhost:5173>.
## Configuration
Runtime configuration is externalised through environment variables (see
`server/config/application-prod.yml.example` for the complete reference).
### Docker Compose (`.env`)
| Variable | Default | Description |
| -------------------------- | ------------------------- | ---------------------------------------------- |
| `PG_DATABASE` | `helix` | PostgreSQL database name |
| `PG_USER` | `helix` | PostgreSQL user |
| `PG_PASSWORD` | `helix` | PostgreSQL password |
| `REDIS_PASSWORD` | *(empty)* | Redis password (leave empty for local Redis) |
| `TOKEN_SECRET` | *(see example)* | JWT signing secret — change in production |
| `MSAL_CLIENT_ID` | *(empty)* | Microsoft Entra ID application (client) ID |
| `MSAL_TENANT_ID` | *(empty)* | Microsoft Entra ID tenant ID |
| `S3_ACCESS_KEY_ID` | *(empty)* | S3 access key (requires enabling asset storage)|
| `S3_SECRET_ACCESS_KEY` | *(empty)* | S3 secret key |
| `SERVER_PORT` | `8080` | Host port mapped to the server container |
| `WEB_PORT` | `80` | Host port mapped to the Caddy container |
Asset storage is disabled in `docker/application-prod.yml` by default. To enable
it, set `app.asset.enabled: true` there and provide the `S3_*` variables.
### Web (`.env`)
| Variable | Default | Description |
| ------------------------------- | ---------------------------------- | ---------------------------------- |
| `VITE_API_BASE_URL` | `/api` | Backend base URL |
| `VITE_MSAL_CLIENT_ID` | *(empty)* | Microsoft Entra ID client ID |
| `VITE_MSAL_TENANT_ID` | *(empty)* | Microsoft Entra ID tenant ID |
| `VITE_DEFAULT_REGION_ABBREVIATION` | `GB` | Default user region (ISO 3166-1 alpha-2) |
| `VITE_APP_TITLE` | `Onixbyte Technology Co., Ltd` | Application title shown in the UI |
## Development
- Architecture and conventions are documented in [AGENTS.md](AGENTS.md).
- See [CONTRIBUTING.md](CONTRIBUTING.md) for how to contribute.
- Database schema is managed with Flyway — add migrations under
`server/src/main/resources/db/migrations/` rather than relying on JPA `ddl-auto`.
## License
[MIT](LICENCE) © OnixByte
+81
View File
@@ -0,0 +1,81 @@
name: helix
services:
postgres:
image: postgres:16-alpine
environment:
POSTGRES_DB: ${PG_DATABASE:-helix}
POSTGRES_USER: ${PG_USER:-helix}
POSTGRES_PASSWORD: ${PG_PASSWORD:-helix}
volumes:
- postgres-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${PG_USER:-helix} -d ${PG_DATABASE:-helix}"]
interval: 5s
timeout: 5s
retries: 10
restart: unless-stopped
redis:
image: redis:7-alpine
command: ["redis-server", "--appendonly", "yes"]
volumes:
- redis-data:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
timeout: 5s
retries: 10
restart: unless-stopped
server:
build:
context: ./server
args:
ARTEFACT_VERSION: ${ARTEFACT_VERSION:-0.0.1}
environment:
SPRING_PROFILES_ACTIVE: prod
PG_HOST: postgres
PG_PORT: "5432"
PG_DATABASE: ${PG_DATABASE:-helix}
PG_USER: ${PG_USER:-helix}
PG_PASSWORD: ${PG_PASSWORD:-helix}
REDIS_HOST: redis
REDIS_PORT: "6379"
REDIS_PASSWORD: ${REDIS_PASSWORD:-}
TOKEN_SECRET: ${TOKEN_SECRET:-please-change-this-secret}
MSAL_CLIENT_ID: ${MSAL_CLIENT_ID:-}
MSAL_TENANT_ID: ${MSAL_TENANT_ID:-}
S3_ACCESS_KEY_ID: ${S3_ACCESS_KEY_ID:-}
S3_SECRET_ACCESS_KEY: ${S3_SECRET_ACCESS_KEY:-}
volumes:
# Mounts the Compose-specific server configuration (Spring Boot picks up
# config/application-{profile}.yml next to the working directory).
- ./docker/application-prod.yml:/app/config/application-prod.yml:ro
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
ports:
- "${SERVER_PORT:-8080}:8080"
restart: unless-stopped
web:
build:
context: ./web
args:
VITE_API_BASE_URL: ${VITE_API_BASE_URL:-/api}
VITE_MSAL_CLIENT_ID: ${VITE_MSAL_CLIENT_ID:-}
VITE_MSAL_TENANT_ID: ${VITE_MSAL_TENANT_ID:-}
VITE_APP_TITLE: ${VITE_APP_TITLE:-Helix}
VITE_DEFAULT_REGION_ABBREVIATION: ${VITE_DEFAULT_REGION_ABBREVIATION:-GB}
depends_on:
- server
ports:
- "${WEB_PORT:-80}:80"
restart: unless-stopped
volumes:
postgres-data:
redis-data:
+77
View File
@@ -0,0 +1,77 @@
# Server configuration used by docker-compose.yaml (mounted into the server
# container at /app/config/application-prod.yml).
#
# All values resolve from environment variables set by the Compose services.
# For a standalone deployment, base your own config on
# server/config/application-prod.yml.example instead.
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:-helix}
# 数据库密码
password: ${PG_PASSWORD:-helix}
app:
asset:
# Disabled by default for a local full-stack run.
# Set `enabled: true` and provide S3_* environment variables to enable
# S3-compatible object storage.
enabled: false
# 鉴权配置
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:-please-change-this-secret}
# 令牌有效期(Ref java.time.Duration)
valid-time: PT2H
# 跨域配置
cors:
# 是否允许身份验证
allow-credentials: true
# 是否允许私有网络
allow-private-network: true
# 允许的请求头列表
allowed-headers: Content-Type
# 允许的请求方法列表(Ref org.springframework.http.HttpMethod)
allowed-methods:
- GET
- POST
- PUT
- PATCH
- DELETE
# 允许的来源域名
allowed-origins: '*'
# 要对外暴露的响应头列表
exposed-headers: X-Authorisation
# 跨域缓存时长
max-age: PT2H
# Captcha 配置
captcha:
# Captcha 长度
length: 6
-9
View File
@@ -1,9 +0,0 @@
# Secrets
.env*
# Dependencies
node_modules
# OS files
.DS_Store
Thumbs.db
@@ -1,22 +0,0 @@
info:
name: Login
type: http
seq: 1
http:
method: POST
url: "{{BaseURL}}/auth/login"
body:
type: json
data: |-
{
"username": "helix",
"password": "123456"
}
auth: inherit
settings:
encodeUrl: true
timeout: 0
followRedirects: true
maxRedirects: 5
@@ -1,7 +0,0 @@
info:
name: Authentication
type: folder
seq: 1
request:
auth: inherit
@@ -1,24 +0,0 @@
info:
name: Add Department
type: http
seq: 1
http:
method: POST
url: "{{BaseURL}}/departments"
body:
type: json
data: |-
{
"name": "Domestic Marketing",
"parentId": 10,
"sort": null,
"status": null
}
auth: inherit
settings:
encodeUrl: true
timeout: 0
followRedirects: true
maxRedirects: 5
@@ -1,7 +0,0 @@
info:
name: Departments
type: folder
seq: 2
request:
auth: inherit
@@ -1,6 +0,0 @@
name: Development
variables:
- name: BaseURL
value: http://localhost:5000
- name: language
value: zh-CN
-26
View File
@@ -1,26 +0,0 @@
opencollection: 1.0.0
info:
name: Helix
config:
proxy:
inherit: true
config:
protocol: http
hostname: ""
port: ""
auth:
username: ""
password: ""
bypassProxy: ""
request:
headers:
- name: Accept-Language
value: "{{language}}"
bundled: false
extensions:
bruno:
ignore:
- node_modules
- .git
-7
View File
@@ -1,7 +0,0 @@
.DS_Store
/node_modules/
# React Router
/.react-router/
/build/
.source
-23
View File
@@ -1,23 +0,0 @@
# Helix API Docs
Vite + React Router + Fumadocs documentation app with OpenAPI support.
## Run
```bash
pnpm install
pnpm dev
```
Open these pages:
- `/api-reference`: OpenAPI interactive reference
- `/docs`: MDX docs pages
## Edit OpenAPI
Manually maintain:
- `openapi/openapi.yaml`
After changes, refresh the `/api-reference` page.
-4
View File
@@ -1,4 +0,0 @@
@import 'tailwindcss';
@import 'fumadocs-ui/css/neutral.css';
@import 'fumadocs-ui/css/preset.css';
@import 'fumadocs-openapi/css/preset.css';
@@ -1,4 +0,0 @@
import { openapi } from '@/lib/openapi';
import { createAPIPage } from 'fumadocs-openapi/ui';
export const APIPage = createAPIPage(openapi);
-15
View File
@@ -1,15 +0,0 @@
import defaultMdxComponents from 'fumadocs-ui/mdx';
import type { MDXComponents } from 'mdx/types';
export function getMDXComponents(components?: MDXComponents) {
return {
...defaultMdxComponents,
...components,
} satisfies MDXComponents;
}
export const useMDXComponents = getMDXComponents;
declare global {
type MDXProvidedComponents = ReturnType<typeof getMDXComponents>;
}
-45
View File
@@ -1,45 +0,0 @@
'use client';
import {
SearchDialog,
SearchDialogClose,
SearchDialogContent,
SearchDialogHeader,
SearchDialogIcon,
SearchDialogInput,
SearchDialogList,
SearchDialogOverlay,
type SharedProps,
} from 'fumadocs-ui/components/dialog/search';
import { useDocsSearch } from 'fumadocs-core/search/client';
import { create } from '@orama/orama';
import { useI18n } from 'fumadocs-ui/contexts/i18n';
function initOrama() {
return create({
schema: { _: 'string' },
language: 'english',
});
}
export default function DefaultSearchDialog(props: SharedProps) {
const { locale } = useI18n();
const { search, setSearch, query } = useDocsSearch({
type: 'static',
initOrama,
locale,
});
return (
<SearchDialog search={search} onSearchChange={setSearch} isLoading={query.isLoading} {...props}>
<SearchDialogOverlay />
<SearchDialogContent>
<SearchDialogHeader>
<SearchDialogIcon />
<SearchDialogInput />
<SearchDialogClose />
</SearchDialogHeader>
<SearchDialogList items={query.data !== 'empty' ? query.data : null} />
</SearchDialogContent>
</SearchDialog>
);
}
-1
View File
@@ -1 +0,0 @@
export { twMerge as cn } from 'tailwind-merge';
-17
View File
@@ -1,17 +0,0 @@
import type { BaseLayoutProps } from 'fumadocs-ui/layouts/shared';
// fill this with your actual GitHub info, for example:
export const gitConfig = {
user: 'fuma-nama',
repo: 'fumadocs',
branch: 'main',
};
export function baseOptions(): BaseLayoutProps {
return {
nav: {
title: 'React Router',
},
githubUrl: `https://github.com/${gitConfig.user}/${gitConfig.repo}`,
};
}
-8
View File
@@ -1,8 +0,0 @@
import path from 'node:path';
import { createOpenAPI } from 'fumadocs-openapi/server';
export const openapi = createOpenAPI({
input: () => ({
helix: path.resolve('./openapi/openapi.yaml'),
}),
});
-15
View File
@@ -1,15 +0,0 @@
import { loader, type InferPageType } from 'fumadocs-core/source';
import { docs } from 'collections/server';
export const source = loader({
source: docs.toFumadocsSource(),
baseUrl: '/docs',
});
export async function getLLMText(page: InferPageType<typeof source>) {
const processed = await page.data.getText('processed');
return `# ${page.data.title} (${page.url})
${processed}`;
}
-8
View File
@@ -1,8 +0,0 @@
import { getLLMText, source } from '@/lib/source';
export async function loader() {
const scan = source.getPages().map(getLLMText);
const scanned = await Promise.all(scan);
return new Response(scanned.join('\n\n'));
}
-6
View File
@@ -1,6 +0,0 @@
import { source } from '@/lib/source';
import { llms } from 'fumadocs-core/source';
export function loader() {
return new Response(llms(source).index());
}
-17
View File
@@ -1,17 +0,0 @@
import type { Route } from './+types/mdx';
import { getLLMText, source } from '@/lib/source';
export async function loader({ params }: Route.LoaderArgs) {
const slugs = params['*'].split('/').filter((v) => v.length > 0);
// remove the appended "index.mdx" that's added to avoid React Router issues
slugs.pop();
const page = source.getPage(slugs);
if (!page) {
return new Response('not found', { status: 404 });
}
return new Response(await getLLMText(page), {
headers: {
'Content-Type': 'text/markdown',
},
});
}
-75
View File
@@ -1,75 +0,0 @@
import {
isRouteErrorResponse,
Links,
Meta,
Outlet,
Scripts,
ScrollRestoration,
} from 'react-router';
import { RootProvider } from 'fumadocs-ui/provider/react-router';
import type { Route } from './+types/root';
import './app.css';
import SearchDialog from '@/components/search';
import NotFound from './routes/not-found';
export const links: Route.LinksFunction = () => [
{ rel: 'preconnect', href: 'https://fonts.googleapis.com' },
{
rel: 'preconnect',
href: 'https://fonts.gstatic.com',
crossOrigin: 'anonymous',
},
{
rel: 'stylesheet',
href: 'https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&display=swap',
},
];
export function Layout({ children }: { children: React.ReactNode }) {
return (
<html lang="en" suppressHydrationWarning>
<head>
<meta charSet="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<Meta />
<Links />
</head>
<body className="flex flex-col min-h-screen">
<RootProvider search={{ SearchDialog }}>{children}</RootProvider>
<ScrollRestoration />
<Scripts />
</body>
</html>
);
}
export default function App() {
return <Outlet />;
}
export function ErrorBoundary({ error }: Route.ErrorBoundaryProps) {
let message = 'Oops!';
let details = 'An unexpected error occurred.';
let stack: string | undefined;
if (isRouteErrorResponse(error)) {
if (error.status === 404) return <NotFound />;
message = 'Error';
details = error.statusText;
} else if (import.meta.env.DEV && error && error instanceof Error) {
details = error.message;
stack = error.stack;
}
return (
<main className="pt-16 p-4 w-full max-w-[1400px] mx-auto">
<h1>{message}</h1>
<p>{details}</p>
{stack && (
<pre className="w-full p-4 overflow-x-auto">
<code>{stack}</code>
</pre>
)}
</main>
);
}
-15
View File
@@ -1,15 +0,0 @@
import { index, route, type RouteConfig } from '@react-router/dev/routes';
export default [
index('routes/home.tsx'),
route('docs/*', 'routes/docs.tsx'),
route('api-reference', 'routes/api-reference.tsx'),
route('api/search', 'routes/search.ts'),
// LLM integration:
route('llms.txt', 'llms/index.ts'),
route('llms-full.txt', 'llms/full.ts'),
route('llms.mdx/docs/*', 'llms/mdx.ts'),
route('*', 'routes/not-found.tsx'),
] satisfies RouteConfig;
@@ -1,29 +0,0 @@
import type { Route } from './+types/api-reference';
import { DocsLayout } from 'fumadocs-ui/layouts/docs';
import { DocsBody, DocsDescription, DocsPage, DocsTitle } from 'fumadocs-ui/layouts/docs/page';
import { source } from '@/lib/source';
import { baseOptions } from '@/lib/layout.shared';
import { APIPage } from '@/components/api-page';
export function meta({}: Route.MetaArgs) {
return [
{ title: 'API Reference' },
{ name: 'description', content: 'OpenAPI reference for Helix API.' },
];
}
export default function ApiReference() {
return (
<DocsLayout {...baseOptions()} tree={source.getPageTree()}>
<DocsPage full>
<DocsTitle>API Reference</DocsTitle>
<DocsDescription>
Edit openapi/openapi.yaml to maintain this API documentation.
</DocsDescription>
<DocsBody>
<APIPage document="helix" />
</DocsBody>
</DocsPage>
</DocsLayout>
);
}
-74
View File
@@ -1,74 +0,0 @@
import type { Route } from './+types/docs';
import { DocsLayout } from 'fumadocs-ui/layouts/docs';
import {
DocsBody,
DocsDescription,
DocsPage,
DocsTitle,
MarkdownCopyButton,
ViewOptionsPopover,
} from 'fumadocs-ui/layouts/docs/page';
import { source } from '@/lib/source';
import browserCollections from 'collections/browser';
import { baseOptions, gitConfig } from '@/lib/layout.shared';
import { useFumadocsLoader } from 'fumadocs-core/source/client';
import { useMDXComponents } from '@/components/mdx';
export async function loader({ params }: Route.LoaderArgs) {
const slugs = params['*'].split('/').filter((v) => v.length > 0);
const page = source.getPage(slugs);
if (!page) throw new Response('Not found', { status: 404 });
return {
slugs: page.slugs,
path: page.path,
pageTree: await source.serializePageTree(source.getPageTree()),
};
}
const clientLoader = browserCollections.docs.createClientLoader({
component(
{ toc, frontmatter, default: Mdx },
// you can define props for the component
{
markdownUrl,
path,
}: {
markdownUrl: string;
path: string;
},
) {
return (
<DocsPage toc={toc}>
<title>{frontmatter.title}</title>
<meta name="description" content={frontmatter.description} />
<DocsTitle>{frontmatter.title}</DocsTitle>
<DocsDescription>{frontmatter.description}</DocsDescription>
<div className="flex flex-row gap-2 items-center border-b -mt-4 pb-6">
<MarkdownCopyButton markdownUrl={markdownUrl} />
<ViewOptionsPopover
markdownUrl={markdownUrl}
githubUrl={`https://github.com/${gitConfig.user}/${gitConfig.repo}/blob/${gitConfig.branch}/content/docs/${path}`}
/>
</div>
<DocsBody>
<Mdx components={useMDXComponents()} />
</DocsBody>
</DocsPage>
);
},
});
export default function Page({ loaderData }: Route.ComponentProps) {
const { pageTree, slugs, path } = useFumadocsLoader(loaderData);
const markdownUrl = `/llms.mdx/docs/${[...slugs, 'index.mdx'].join('/')}`;
return (
<DocsLayout {...baseOptions()} tree={pageTree}>
{clientLoader.useContent(loaderData.path, {
markdownUrl,
path,
})}
</DocsLayout>
);
}
-35
View File
@@ -1,35 +0,0 @@
import type { Route } from './+types/home';
import { HomeLayout } from 'fumadocs-ui/layouts/home';
import { Link } from 'react-router';
import { baseOptions } from '@/lib/layout.shared';
export function meta({}: Route.MetaArgs) {
return [
{ title: 'New React Router App' },
{ name: 'description', content: 'Welcome to React Router!' },
];
}
export default function Home() {
return (
<HomeLayout {...baseOptions()}>
<div className="p-4 flex flex-col items-center justify-center text-center flex-1">
<h1 className="text-xl font-bold mb-2">Helix API Documentation</h1>
<p className="text-fd-muted-foreground mb-4">
Powered by Vite, React Router and Fumadocs OpenAPI.
</p>
<div className="flex items-center gap-3">
<Link
className="text-sm bg-fd-primary text-fd-primary-foreground rounded-full font-medium px-4 py-2.5"
to="/api-reference"
>
Open API Reference
</Link>
<Link className="text-sm rounded-full font-medium px-4 py-2.5 border" to="/docs">
Open MDX Docs
</Link>
</div>
</div>
</HomeLayout>
);
}
-25
View File
@@ -1,25 +0,0 @@
import type { Route } from './+types/not-found';
import { HomeLayout } from 'fumadocs-ui/layouts/home';
import { Link } from 'react-router';
import { baseOptions } from '@/lib/layout.shared';
export function meta({}: Route.MetaArgs) {
return [{ title: 'Not Found' }];
}
export default function NotFound() {
return (
<HomeLayout {...baseOptions()}>
<div className="p-4 flex flex-col items-center justify-center text-center flex-1">
<h1 className="text-xl font-bold mb-2">Not Found</h1>
<p className="text-fd-muted-foreground mb-4">This page could not be found.</p>
<Link
className="text-sm bg-fd-primary text-fd-primary-foreground rounded-full font-medium px-4 py-2.5"
to="/docs"
>
Back to Docs
</Link>
</div>
</HomeLayout>
);
}
-10
View File
@@ -1,10 +0,0 @@
import { createFromSource } from 'fumadocs-core/search/server';
import { source } from '@/lib/source';
const server = createFromSource(source, {
language: 'english',
});
export async function loader() {
return server.staticGET();
}
-32
View File
@@ -1,32 +0,0 @@
---
title: Hello World
description: |
Your first `document`
You'll love it!
---
Hey there! Fumadocs is the docs framework that also works on React Router!
## Heading
Hello World
<Cards>
<Card title="Learn more about React Router" href="https://reactrouter.com" />
<Card title="Learn more about Fumadocs" href="https://fumadocs.dev" />
</Cards>
```ts
console.log('I love React!');
```
### Heading
#### Heading
| Head | Description |
| ------------------------------- | ----------------------------------- |
| `hello` | Hello World |
| very **important** | Hey |
| _Surprisingly_ | Fumadocs |
| very long text that looks weird | hello world hello world hello world |
-24
View File
@@ -1,24 +0,0 @@
---
title: Test
description: A document to test Fumadocs
---
Hey there!
## Cards
<Cards>
<Card title="Learn more about Next.js" href="https://nextjs.org/docs" />
<Card title="Learn more about Fumadocs" href="https://fumadocs.dev" />
</Cards>
### CodeBlock
```js
console.log('Hello World');
```
#### List
- Hello
- World
-32
View File
@@ -1,32 +0,0 @@
openapi: 3.1.0
info:
title: Helix API
version: 0.1.0
description: |
Initial OpenAPI skeleton.
Maintain this file manually.
servers:
- url: http://localhost:5000
description: Local development
paths:
/health:
get:
summary: Health check
operationId: healthCheck
tags:
- System
responses:
'200':
description: Service is available
content:
application/json:
schema:
type: object
properties:
status:
type: string
example: ok
required:
- status
components:
securitySchemes: {}
-39
View File
@@ -1,39 +0,0 @@
{
"name": "docs",
"private": true,
"type": "module",
"scripts": {
"build": "react-router build",
"dev": "react-router dev",
"start": "serve ./build/client --config ../../serve.json",
"types:check": "react-router typegen && fumadocs-mdx && tsc --noEmit",
"postinstall": "fumadocs-mdx"
},
"dependencies": {
"@orama/orama": "^3.1.18",
"@react-router/node": "^7.13.1",
"fumadocs-core": "16.7.5",
"fumadocs-mdx": "14.2.11",
"fumadocs-openapi": "^10.4.1",
"fumadocs-ui": "16.7.5",
"isbot": "^5.1.36",
"lucide-react": "^0.577.0",
"react": "^19.2.4",
"react-dom": "^19.2.4",
"react-router": "^7.13.1",
"tailwind-merge": "^3.5.0"
},
"devDependencies": {
"@react-router/dev": "^7.13.1",
"@tailwindcss/vite": "^4.2.2",
"@types/mdx": "^2.0.13",
"@types/node": "^25.5.0",
"@types/react": "^19.2.14",
"@types/react-dom": "^19.2.3",
"react-router-devtools": "^6.2.0",
"serve": "^14.2.6",
"tailwindcss": "^4.2.2",
"typescript": "^5.9.3",
"vite": "^8.0.1"
}
}
-6401
View File
File diff suppressed because it is too large. Load diff
Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

-27
View File
@@ -1,27 +0,0 @@
import type { Config } from '@react-router/dev/config';
import { glob } from 'node:fs/promises';
import { createGetUrl, getSlugs } from 'fumadocs-core/source';
const getUrl = createGetUrl('/docs');
export default {
ssr: false,
future: {
v8_middleware: true,
},
async prerender({ getStaticPaths }) {
const paths: string[] = [];
const excluded: string[] = ['/api-reference', '/api-reference/'];
for (const path of getStaticPaths()) {
if (!excluded.includes(path)) paths.push(path);
}
for await (const entry of glob('**/*.mdx', { cwd: 'content/docs' })) {
const slugs = getSlugs(entry);
paths.push(getUrl(slugs), `/llms.mdx/docs/${[...slugs, 'index.mdx'].join('/')}`);
}
return paths;
},
} satisfies Config;
-3
View File
@@ -1,3 +0,0 @@
{
"rewrites": [{ "source": "/**", "destination": "/__spa-fallback.html" }]
}
-12
View File
@@ -1,12 +0,0 @@
import { defineConfig, defineDocs } from 'fumadocs-mdx/config';
export const docs = defineDocs({
dir: 'content/docs',
docs: {
postprocess: {
includeProcessedMarkdown: true,
},
},
});
export default defineConfig();
-23
View File
@@ -1,23 +0,0 @@
{
"include": ["**/*", "**/.server/**/*", "**/.client/**/*", ".react-router/types/**/*"],
"compilerOptions": {
"lib": ["DOM", "DOM.Iterable", "ES2022"],
"types": ["node", "vite/client"],
"target": "esnext",
"module": "esnext",
"moduleResolution": "bundler",
"jsx": "react-jsx",
"rootDirs": [".", "./.react-router/types"],
"baseUrl": ".",
"paths": {
"@/*": ["./app/*"],
"collections/*": [".source/*"]
},
"esModuleInterop": true,
"verbatimModuleSyntax": true,
"noEmit": true,
"resolveJsonModule": true,
"skipLibCheck": true,
"strict": true
}
}
-12
View File
@@ -1,12 +0,0 @@
import { reactRouter } from '@react-router/dev/vite';
import tailwindcss from '@tailwindcss/vite';
import { defineConfig } from 'vite';
import mdx from 'fumadocs-mdx/vite';
import * as MdxConfig from './source.config';
export default defineConfig({
plugins: [mdx(MdxConfig), tailwindcss(), reactRouter()],
resolve: {
tsconfigPaths: true,
},
});
-150
View File
@@ -1,150 +0,0 @@
### 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
### 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/
### Gradle
.gradle
**/build/
!**/src/**/build/
!gradle/
!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
-105
View File
@@ -1,105 +0,0 @@
### 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
# Node libraries
node_modules/
# Config
.env.local
.env.*.local
File renamed without changes.
@@ -5,6 +5,7 @@ WORKDIR /home/app
COPY gradlew .
COPY gradle gradle
COPY gradle.properties .
COPY build.gradle.kts .
COPY settings.gradle.kts .
COPY gradle/libs.versions.toml gradle/libs.versions.toml
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
View File
File renamed without changes.
View File
File renamed without changes.
File renamed without changes.
Loaded 100 of 315 files, more files were not shown because too many files have changed in this diff. Show more