|
1 | | -# Ezclaim |
2 | | - |
3 | | -A Spring Boot service for managing claims with MongoDB storage, S3-compatible object storage for photos, and a clean MVC/API structure. Includes Bruno API tests and dev tooling for quick startup. |
4 | | - |
5 | | -## Tech Stack |
6 | | -- Spring Boot 3.5.x (Java 21+/24 target) |
7 | | -- MongoDB (Spring Data Mongo) |
8 | | -- S3-compatible object storage via AWS SDK v2 (works with AWS S3, MinIO, etc.) |
9 | | -- Messaging: Spring Cloud Stream + Kafka (Redpanda in dev) |
10 | | -- Security: Spring Security OAuth2 Resource Server (JWT HS256) |
11 | | -- Build: Maven Wrapper (`./mvnw`) |
12 | | -- API tests: Bruno (`.bru` files under `bruno/`) |
13 | | - |
14 | | -## Quick Start (Dev) |
15 | | -1) Prereqs: Docker, Java 21+ (build is set to Java 24), optional direnv. |
16 | | -2) Start dev services (MongoDB 8 + MinIO + Redpanda/Kafka): |
17 | | - - `docker compose -f docker-compose.dev.yml up -d` |
18 | | -3) Load project env (dev profile): |
19 | | - - Install direnv, then in repo root: `direnv allow` (loads `.envrc` => `SPRING_PROFILES_ACTIVE=dev`). |
20 | | -4) Run the app: |
21 | | - - `./mvnw spring-boot:run` |
22 | | - |
23 | | -Dev URLs |
24 | | -- API base: `http://localhost:8080` |
25 | | -- MinIO Console: `http://localhost:9001` |
26 | | -- S3 endpoint: `http://localhost:9000` |
27 | | -- Mongo: `mongodb://...` from `application-dev.yml` |
28 | | -- Swagger UI: `http://localhost:8080/swagger-ui/index.html` |
29 | | -- OpenAPI JSON: `http://localhost:8080/v3/api-docs` |
30 | | - - OpenAPI YAML: `http://localhost:8080/v3/api-docs.yaml` |
31 | | - |
32 | | -Generate static OpenAPI YAML |
33 | | -- Start the app locally on port 8080, then run: |
34 | | - - `./mvnw -Popenapi -DskipTests springdoc-openapi:generate` |
35 | | -- Output: `target/openapi.yaml` |
36 | | - |
37 | | -## Configuration |
38 | | -Spring profiles are used to separate dev and prod configuration: |
39 | | -- `src/main/resources/application-dev.yml` (local dev) |
40 | | -- `src/main/resources/application-prod.yml` (deployment) |
41 | | - |
42 | | -Mongo (dev) |
43 | | -- `spring.data.mongodb.uri=mongodb://ezclaim:E2Claim@localhost:27017/ezclaim?authSource=admin` |
44 | | - - If authentication fails, clear the docker volume or align the URI with the existing root user in your volume. |
45 | | - |
46 | | -Object Store (generic S3) |
47 | | -- Properties prefix: `app.objectstore.*` |
48 | | - - `endpoint`: omit for AWS S3; set to `http://localhost:9000` for MinIO dev |
49 | | - - `region`: e.g. `us-east-1` |
50 | | - - `access-key`, `secret-key`: credentials |
51 | | - - `bucket`: default bucket name |
52 | | - - `path-style`: `true` for S3-compatible services like MinIO |
53 | | - - `ensure-bucket`: set `true` in dev to auto-create; keep `false` in prod |
54 | | - |
55 | | -Prod environment variables expected (see `application-prod.yml`): |
56 | | -- `SPRING_DATA_MONGODB_URI` |
57 | | -- `KAFKA_BOOTSTRAP_SERVERS` |
58 | | -- `APP_OBJECTSTORE_ENDPOINT` (omit for AWS S3) |
59 | | -- `APP_OBJECTSTORE_REGION` |
60 | | -- `APP_OBJECTSTORE_ACCESS_KEY` |
61 | | -- `APP_OBJECTSTORE_SECRET_KEY` |
62 | | -- `APP_OBJECTSTORE_BUCKET` |
63 | | -- `APP_OBJECTSTORE_PATH_STYLE` |
64 | | -- `APP_OBJECTSTORE_ENSURE_BUCKET` |
65 | | -- `APP_JWT_SECRET` (required for JWT HS256) |
66 | | -- `APP_JWT_ALG` (optional, default `HS256`) |
67 | | -- `APP_JWT_TTL` (optional, ISO-8601 duration, default `PT12H`) |
68 | | - |
69 | | -## Domain & API |
70 | | -Entities |
71 | | -- Claim: `title`, `description`, `status`, `createdAt`, `updatedAt`, references to `photos[]`, `tags[]` (stored separately) |
72 | | -- Photo: `bucket`, `key`, `uploadedAt` (S3 metadata) |
73 | | -- Tag: `label`, `color` |
74 | | - |
75 | | -Key Endpoints (REST) |
76 | | -- Tags: `GET/POST/PUT/DELETE /api/tags[/id]` |
77 | | -- Photos: |
78 | | - - `POST /api/photos/presign-upload` → returns presigned PUT URL |
79 | | - - `POST /api/photos` → create a Photo record (after you upload) |
80 | | - - `GET /api/photos/{id}/download-url` → presigned GET URL |
81 | | - - `GET/DELETE /api/photos[/id]` |
82 | | -- Claims: `GET/POST/PUT/DELETE /api/claims[/id]` (accepts `photoIds[]` and `tagIds[]`) |
83 | | - - Audit Events: |
84 | | - - `GET /api/audit-events` (filters: `entityType`, `entityId`, `action`, `from`, `to`; paging `page`, `size`; sorting `sort=field,asc|desc`) |
85 | | - - `GET /api/audit-events/{id}` |
86 | | - |
87 | | -Auth (for Audit Events) |
88 | | -- `POST /api/auth/login` with JSON `{ "username": "admin", "password": "ezclaim-password" }` |
89 | | -- Returns `{ token, tokenType: "Bearer", expiresAt }` |
90 | | -- Include header `Authorization: Bearer <token>` for all `/api/audit-events/**` requests. |
91 | | - |
92 | | -Security notes |
93 | | -- Uses standard JWT (HS256) via Spring Security OAuth2 Resource Server. |
94 | | -- Dev secret and TTL are configured in `application-dev.yml` under `app.security.jwt.*`. |
95 | | -- In prod, set `APP_JWT_SECRET` (and optionally `APP_JWT_TTL`). Plan for secret rotation and a shorter TTL. |
96 | | - |
97 | | -Built-in Users (in-memory) |
98 | | -- Anonymous (no token): |
99 | | - - Can read claims: `GET /api/claims`, `GET /api/claims/{id}` (response includes related photos/tags). |
100 | | -- `reader` / `reader-pass`: |
101 | | - - Scopes: `AUDIT`, `TAG_READ`, `PHOTO_READ`, `CLAIM_READ` |
102 | | - - Can: read audit events; read tags; read photos; read all claims. |
103 | | -- `admin` / `ezclaim-password`: |
104 | | - - Scopes: all of reader plus `CLAIM_WRITE`, `TAG_WRITE`, `PHOTO_DELETE`, `PHOTO_WRITE` |
105 | | - - Can: update/create/delete claims; CRUD tags; delete photos; presign upload & create photo record. |
106 | | - |
107 | | -Endpoint Authorization Summary |
108 | | -- Audit: `/api/audit-events/**` → requires `SCOPE_AUDIT` |
109 | | -- Claims: |
110 | | - - `GET /api/claims` → `SCOPE_CLAIM_READ` |
111 | | - - `GET /api/claims/{id}` → public |
112 | | - - `POST/PUT/DELETE /api/claims[/id]` → `SCOPE_CLAIM_WRITE` |
113 | | -- Tags: |
114 | | - - `GET /api/tags[/id]` → `SCOPE_TAG_READ` |
115 | | - - `POST/PUT/DELETE /api/tags[/id]` → `SCOPE_TAG_WRITE` |
116 | | -- Photos: |
117 | | - - `GET /api/photos[/id]`, `GET /api/photos/{id}/download-url` → `SCOPE_PHOTO_READ` |
118 | | - - `DELETE /api/photos/{id}` → `SCOPE_PHOTO_DELETE` |
119 | | - - `POST /api/photos/presign-upload`, `POST /api/photos` → `SCOPE_PHOTO_WRITE` |
120 | | - |
121 | | -## Bruno API Tests |
122 | | -- Collection root: `bruno/` |
123 | | -- Environments: `bruno/environments/dev.bru`, `bruno/environments/prod.bru` (uses `baseUrl`) |
124 | | -- Requests grouped in `bruno/Auth`, `bruno/Tags`, `bruno/Photos`, `bruno/Claims`, `bruno/Audit Events` |
125 | | -- Open the `bruno/` folder in Bruno, choose an environment, then run tests in order (Auth → Tags → Photos → Claims → Audit Events). The Audit Events folder includes list, paginated list, filter example (with default variables), and get-by-id. The list/filter scripts capture the first event id to `{{auditEventId}}` for convenience. |
126 | | - |
127 | | -Audit Events pipeline |
128 | | -- Change events are published to Kafka topic `audit.events` (binding `auditEvents-out-0`) by `MongoChangePublisher`. |
129 | | -- The Consumer function `auditEvents` persists them to Mongo (binding `auditEvents-in-0`). |
130 | | -- Dev uses Redpanda on `localhost:9092`; adjust `KAFKA_BOOTSTRAP_SERVERS` as needed. |
131 | | - |
132 | | -## Build & Test |
133 | | -- Unit tests: `./mvnw test` |
134 | | -- Package: `./mvnw -DskipTests package` |
135 | | - |
136 | | -## Notes |
137 | | -- If the app starts before MinIO, bucket provisioning waits up to ~60s with retries. |
138 | | -- For Mongo auth errors in dev, the most common cause is a reused volume created with different credentials; reset the volume or align the URI. |
139 | | - |
140 | | -## License |
141 | | -This project is licensed under the WTFPL. See `LICENCE` for details. |
| 1 | +# EzClaim 管理后台 |
| 2 | + |
| 3 | +Next.js 14 构建的 EzClaim 管理端,提供管理员登录、标签管理、报销单审批以及审计事件查询等能力。后端接口基于项目根目录的 `api.json` (OpenAPI 3.1)。 |
| 4 | + |
| 5 | +## 功能亮点 |
| 6 | + |
| 7 | +- 🔐 管理员登录,使用 JWT 存储于 HttpOnly Cookie |
| 8 | +- 🏷️ 标签管理:创建、查看、删除标签 |
| 9 | +- 💼 报销单管理:支持搜索、筛选、排序,状态流转遵守服务端规则 |
| 10 | +- 📊 实时概览:按状态统计报销单数量 |
| 11 | +- 🛠️ 审计事件查询:多条件过滤、分页展示、详细 JSON 查看 |
| 12 | + |
| 13 | +## 快速开始 |
| 14 | + |
| 15 | +```bash |
| 16 | +npm install |
| 17 | +npm run dev |
| 18 | +``` |
| 19 | + |
| 20 | +默认后端地址为 `http://localhost:8080`,如需修改可在启动前设置: |
| 21 | + |
| 22 | +```bash |
| 23 | +export API_BASE_URL="http://your-backend:8080" |
| 24 | +``` |
| 25 | + |
| 26 | +或在 `.env.local` 中声明 `API_BASE_URL`/`NEXT_PUBLIC_API_BASE_URL`。 |
| 27 | + |
| 28 | +## 运行前提 |
| 29 | + |
| 30 | +- Node.js 18+ |
| 31 | +- 后端服务(默认端口 8080,可通过 `api.json` 查看接口定义) |
| 32 | +- Demo 账户:`admin / ezclaim-password` 或 `reader / reader-pass` |
| 33 | + |
| 34 | +## 脚本 |
| 35 | + |
| 36 | +| 命令 | 说明 | |
| 37 | +| ------------ | -------------------- | |
| 38 | +| `npm run dev` | 开发模式 (`localhost:3000`) | |
| 39 | +| `npm run build` | 生产构建 | |
| 40 | +| `npm run start` | 启动生产环境服务器 | |
| 41 | +| `npm run lint` | 执行 ESLint 检查 | |
| 42 | + |
| 43 | +## 目录结构 |
| 44 | + |
| 45 | +``` |
| 46 | +app/ # App Router 路由 |
| 47 | + (auth)/login # 登录页 |
| 48 | + (dashboard)/ # 受保护的业务页面 |
| 49 | +components/ # UI 组件与布局 |
| 50 | +lib/ # API 客户端、配置、工具函数 |
| 51 | +middleware.ts # 保护路由的中间件 |
| 52 | +``` |
| 53 | + |
| 54 | +## 认证机制 |
| 55 | + |
| 56 | +- 登录后通过服务端调用 `/api/auth/login` 获取 JWT |
| 57 | +- Token 以 HttpOnly Cookie (`ezclaim_token`) 形式存储 |
| 58 | +- `middleware.ts` 拦截未登录访问并重定向至 `/login` |
| 59 | + |
| 60 | +## 注意事项 |
| 61 | + |
| 62 | +- 所有与后端交互的操作使用 Next.js Server Actions,并在成功后 `router.refresh()` + `revalidatePath` |
| 63 | +- 报销单状态流转遵循后端 `ClaimService` 的约束: |
| 64 | + - `SUBMITTED → APPROVED/REJECTED` |
| 65 | + - `APPROVED → PAID/REJECTED` |
| 66 | +- `Audit Events` 页面采用 GET 参数驱动,可直接分享链接复现查询条件 |
| 67 | + |
| 68 | +欢迎根据业务需要扩展更多管理能力,例如报销单详情编辑、批量操作等。 |
0 commit comments