[CLAUDE] Phase1: foundation - BE Clean Arch + Identity + JWT + 2 FE React + login E2E

Backend (.NET 10):
- Domain: BaseEntity/AuditableEntity, ContractType/Phase/ApprovalDecision enums, User/Role (Identity<Guid>), AppRoles (12 const)
- Application: IApplicationDbContext/ICurrentUser/IDateTime/IJwtTokenService, custom exceptions, ValidationBehavior (MediatR pipeline), Auth CQRS (Login/Refresh/Me), DependencyInjection
- Infrastructure: ApplicationDbContext (IdentityDbContext), AuditingInterceptor (auto audit + soft delete), DbInitializer (seed 12 role + admin), DesignTimeDbContextFactory, JwtTokenService, DateTimeService, DI
- Api: CurrentUserService, GlobalExceptionMiddleware (ProblemDetails), AuthController, Program.cs rewrite (Serilog + JWT + CORS + Swagger), appsettings + launchSettings (port 5443)
- Migration Init applied to SolutionErp_Dev LocalDB

Frontend (React 19 + Vite 8 + Tailwind 4):
- fe-admin (:8082 blue) + fe-user (:8080 emerald) - shared structure, khac menu + brand color
- Tailwind 4 via @tailwindcss/vite plugin, theme brand colors
- AuthContext (localStorage token), ProtectedRoute, Layout (sidebar + header)
- UI kit: Button/Input/Label (CVA + Tailwind)
- LoginPage voi toast error, DashboardPage/InboxPage placeholder
- Axios interceptor: auto Bearer + 401 redirect
- TanStack Query client, React Router 7, Sonner toast

Package downgrades (do .NET 10 / TS 6 compat):
- MediatR 14 -> 12.4.1 (v14 breaking changes)
- Swashbuckle 10 -> 6.9.0 (v10 khong tuong thich OpenApi 2)
- Removed Microsoft.AspNetCore.OpenApi (conflict voi Swashbuckle)

E2E verified: POST /api/auth/login qua Vite proxy ca 2 FE -> JWT + user info

Credentials seed: admin@solutionerp.local / Admin@123456

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
pqhuy1987
2026-04-21 10:59:44 +07:00
parent 25dad7f36f
commit 702411fcc8
85 changed files with 10326 additions and 964 deletions

View File

@ -0,0 +1,128 @@
# Session 2026-04-21 11:00 — Phase 1 Foundation complete
**Dev:** Claude (Opus 4.7)
**Duration:** ~1h15m
**Base commit:** `25dad7f` (Phase 0)
**Commit target:** ~30 files added/modified
## Làm được
### Backend — Clean Architecture hoàn chỉnh
**Domain layer:**
- `Common/BaseEntity.cs`, `AuditableEntity.cs` (Id Guid + audit fields + soft delete)
- `Contracts/` enums: `ContractType`, `ContractPhase` (9 phase), `ApprovalDecision`
- `Identity/User.cs : IdentityUser<Guid>` + `Role.cs : IdentityRole<Guid>`
- `Identity/AppRoles.cs` — 12 role constants (Admin, Drafter, DeptManager, ProjectManager, Procurement, CostControl, Finance, Accounting, Equipment, Director, AuthorizedSigner, HrAdmin)
**Application layer:**
- Interfaces: `IApplicationDbContext`, `ICurrentUser`, `IDateTime`, `IJwtTokenService`
- Exceptions: `NotFoundException`, `ValidationException`, `ForbiddenException`, `UnauthorizedException`, `ConflictException`
- `ValidationBehavior` (MediatR pipeline — FluentValidation)
- Auth slice: `LoginCommand`, `RefreshTokenCommand`, `GetMeQuery` (CQRS + handlers + validators)
- `DependencyInjection.cs` — MediatR + FluentValidation wire-up
**Infrastructure layer:**
- `Services/DateTimeService.cs` (IDateTime impl)
- `Identity/JwtSettings.cs` + `JwtTokenService.cs` (HS256, refresh token 7d, access 1h)
- `Persistence/ApplicationDbContext.cs : IdentityDbContext<User, Role, Guid>, IApplicationDbContext`
- `Persistence/Interceptors/AuditingInterceptor.cs` — auto CreatedAt/UpdatedAt/DeletedBy + soft delete
- `Persistence/DbInitializer.cs` — apply migrations + seed 12 roles + admin user
- `Persistence/DesignTimeDbContextFactory.cs` — cho `dotnet ef` không cần Program.cs
- `DependencyInjection.cs` — wire DbContext + Identity + JWT service
**Api layer:**
- `Services/CurrentUserService.cs` — impl `ICurrentUser` từ HttpContext
- `Middleware/GlobalExceptionMiddleware.cs` — map exception → ProblemDetails JSON
- `Controllers/AuthController.cs``POST /api/auth/login`, `/refresh`, `GET /me`, `POST /logout`
- `Program.cs` rewrite: Serilog, JWT auth, CORS (2 FE origin), Swagger + Bearer security, DB init
- `appsettings.json` + `appsettings.Development.json` (connection string + JWT)
- `launchSettings.json` update port 5443
**Migration:**
- `Init` migration → `SolutionErp.Infrastructure/Persistence/Migrations/20260421034520_Init.cs`
- Applied to `SolutionErp_Dev` LocalDB ✅
### Frontend — 2 app React 19 + Vite + Tailwind 4
**Shared structure (fe-admin + fe-user):**
- Vite config: port + strictPort + `/api` proxy + `@/*` alias + Tailwind 4 plugin
- `src/index.css` — Tailwind import + theme brand colors (admin=blue, user=emerald)
- `src/lib/api.ts` — axios instance + JWT request interceptor + 401 redirect
- `src/lib/cn.ts` — clsx + tailwind-merge helper
- `src/types/auth.ts` — mirror BE DTOs
- `src/contexts/AuthContext.tsx` — login/logout, localStorage token, bootstrap on mount
- `src/components/ProtectedRoute.tsx` — redirect login nếu chưa auth
- `src/components/Layout.tsx` — sidebar + header, lucide icons, user info + logout
- `src/components/ui/Button.tsx`, `Input.tsx`, `Label.tsx` — mini shadcn (CVA + Tailwind)
- `src/pages/LoginPage.tsx` — form login với toast error
- `src/App.tsx` — Router + AuthProvider + Toaster (sonner)
- `src/main.tsx` — QueryClient (TanStack Query) + StrictMode
**Khác biệt 2 app:**
- fe-admin: port 8082, brand blue, menu [Tổng quan / HĐ / NCC / Users / Settings], route `/dashboard`
- fe-user: port 8080, brand emerald, menu [Chờ xử lý / HĐ của tôi / Tạo HĐ mới], route `/inbox`
- localStorage key riêng (`solution-erp-admin-token` vs `solution-erp-user-token`)
### Packages cài thêm
**Backend:**
- Microsoft.Extensions.Identity.Stores (Domain)
- Microsoft.AspNetCore.Identity.EntityFrameworkCore (Infrastructure)
- System.IdentityModel.Tokens.Jwt (Infrastructure)
- Microsoft.Extensions.DependencyInjection.Abstractions (Application)
- Microsoft.EntityFrameworkCore.Design (Api — `PrivateAssets=all`)
**Frontend (cả 2 app):**
- tailwindcss 4 + @tailwindcss/vite
- axios, @tanstack/react-query, react-router-dom 7, sonner, lucide-react
- clsx, tailwind-merge, class-variance-authority
## Bug gặp phải + fix
| Bug | Root cause | Fix |
|---|---|---|
| `IApplicationDbContext.cs` error CS0234 EntityFrameworkCore | Thừa `using Microsoft.EntityFrameworkCore` nhưng không cần (chỉ có `SaveChangesAsync`) | Bỏ using |
| `ValidationBehavior` ambiguous `ValidationException` | FluentValidation + app custom cùng tên | `using ValidationException = ...Custom.ValidationException;` |
| `AddDefaultTokenProviders()` không có trong `IdentityCore` | Identity Core không include token providers | Remove call — sẽ add lại Phase 4 (password reset) |
| DbContext design-time không resolve được options | `AddDbContext` cần runtime config | Thêm `DesignTimeDbContextFactory` |
| Swagger 404 + IMediator not resolved | **Program.cs không persist** — Write tool ghi nhưng file vẫn là default scaffold (nghi Dropbox revert) | Write lại Program.cs — vấn đề giải quyết |
| `Microsoft.OpenApi.Models` namespace không có | Microsoft.OpenApi 2.0 (breaking change) đi kèm Microsoft.AspNetCore.OpenApi 10 | Remove `Microsoft.AspNetCore.OpenApi` (dùng Swashbuckle thay thế) |
| Swashbuckle 10.1.7 không tương thích OpenApi 2 | Swashbuckle chưa support OpenApi 2.x | Downgrade Swashbuckle → 6.9.0 |
| MediatR 14.1.0 không có `AddMediatR` extension | MediatR v14 refactored | Downgrade → 12.4.1 |
| `baseUrl` deprecated trong TS 6 | TypeScript 6 deprecates baseUrl | Bỏ baseUrl, chỉ giữ paths (works relative to tsconfig location) |
## E2E verified (2026-04-21 10:56)
`POST http://localhost:8082/api/auth/login` (fe-admin Vite proxy) → 200 + JWT
`POST http://localhost:8080/api/auth/login` (fe-user Vite proxy) → 200 + JWT
`GET http://localhost:5443/api/auth/me` với Bearer → 200 + user info
`GET http://localhost:5443/swagger/v1/swagger.json` → 200
✅ Seed admin (`admin@solutionerp.local` / `Admin@123456`) + 12 roles chạy tự động
## Bonus: package constraints ghi lại
Phase 1 đã phát hiện vài package không tương thích .NET 10 / TS 6 / Vite 8:
- Swashbuckle 6.9.0 (không 10.x)
- MediatR 12.4.1 (không 14.x)
- tsconfig không dùng `baseUrl` (TS 6 deprecate)
Nếu Phase 2-5 thấy package mới lại bug, thử **downgrade về stable** trước khi debug.
## Handoff cho session tiếp theo
**Phase 1 đợt 2** — xem STATUS.md section "Next up":
1. CRUD master data (Supplier/Project/Department)
2. Permission Matrix
3. Contract entity draft
**Đọc trước khi code:**
1. [docs/STATUS.md](../../STATUS.md)
2. [docs/workflow-contract.md](../../workflow-contract.md) (domain context)
3. Code vừa viết ở `src/Backend/SolutionErp.*/` + `fe-admin/src/`, `fe-user/src/`
**Blocker vẫn còn:**
- ⏳ Gitea remote URL
**Credentials:**
- `admin@solutionerp.local` / `Admin@123456`