Sure! Pl
This commit is contained in:
1323
.cursor/plans/enterprise_f5e39e08.plan.md
Normal file
1323
.cursor/plans/enterprise_f5e39e08.plan.md
Normal file
File diff suppressed because it is too large
Load Diff
572
.cursor/skills/comment-code/SKILL.md
Normal file
572
.cursor/skills/comment-code/SKILL.md
Normal file
@@ -0,0 +1,572 @@
|
||||
---
|
||||
name: comment-code
|
||||
description: Add bilingual code comments in Vietnamese and English for better documentation. Use when adding comments to code, documenting functions/classes, or when user requests Vietnamese/English documentation.
|
||||
---
|
||||
|
||||
# Bilingual Code Comments
|
||||
|
||||
Add comprehensive code comments in both Vietnamese and English to improve code readability for international and Vietnamese teams.
|
||||
|
||||
## When to Use
|
||||
|
||||
- Adding comments to new code
|
||||
- Documenting existing code
|
||||
- Creating JSDoc/TSDoc documentation
|
||||
- Writing function/class descriptions
|
||||
- Explaining complex logic
|
||||
- Adding inline comments
|
||||
|
||||
## Comment Format
|
||||
|
||||
### Single-line Comments
|
||||
|
||||
```typescript
|
||||
// EN: Initialize database connection
|
||||
// VI: Khởi tạo kết nối database
|
||||
const db = await createConnection();
|
||||
```
|
||||
|
||||
### Multi-line Comments
|
||||
|
||||
```typescript
|
||||
/**
|
||||
* EN: Validates user credentials and returns JWT token
|
||||
* VI: Xác thực thông tin đăng nhập và trả về JWT token
|
||||
*
|
||||
* @param email - User email address / Địa chỉ email người dùng
|
||||
* @param password - User password / Mật khẩu người dùng
|
||||
* @returns JWT token / Mã JWT token
|
||||
* @throws AuthenticationError if credentials invalid / Lỗi xác thực nếu thông tin không hợp lệ
|
||||
*/
|
||||
async function login(email: string, password: string): Promise<string> {
|
||||
// Implementation
|
||||
}
|
||||
```
|
||||
|
||||
## Language-Specific Patterns
|
||||
|
||||
### TypeScript/JavaScript
|
||||
|
||||
#### Function Documentation
|
||||
```typescript
|
||||
/**
|
||||
* EN: Calculates the total price including tax and discount
|
||||
* VI: Tính tổng giá bao gồm thuế và giảm giá
|
||||
*
|
||||
* @param basePrice - Original price / Giá gốc
|
||||
* @param taxRate - Tax rate (0-1) / Tỷ lệ thuế (0-1)
|
||||
* @param discount - Discount amount / Số tiền giảm giá
|
||||
* @returns Final price / Giá cuối cùng
|
||||
*/
|
||||
function calculateTotal(
|
||||
basePrice: number,
|
||||
taxRate: number,
|
||||
discount: number
|
||||
): number {
|
||||
// EN: Apply discount first
|
||||
// VI: Áp dụng giảm giá trước
|
||||
const discountedPrice = basePrice - discount;
|
||||
|
||||
// EN: Then calculate tax
|
||||
// VI: Sau đó tính thuế
|
||||
const tax = discountedPrice * taxRate;
|
||||
|
||||
return discountedPrice + tax;
|
||||
}
|
||||
```
|
||||
|
||||
#### Class Documentation
|
||||
```typescript
|
||||
/**
|
||||
* EN: Handles user authentication and authorization
|
||||
* VI: Xử lý xác thực và phân quyền người dùng
|
||||
*/
|
||||
export class AuthService {
|
||||
/**
|
||||
* EN: JWT secret key from environment
|
||||
* VI: Khóa bí mật JWT từ biến môi trường
|
||||
*/
|
||||
private readonly jwtSecret: string;
|
||||
|
||||
/**
|
||||
* EN: Initialize auth service with configuration
|
||||
* VI: Khởi tạo service xác thực với cấu hình
|
||||
*
|
||||
* @param config - Authentication configuration / Cấu hình xác thực
|
||||
*/
|
||||
constructor(config: AuthConfig) {
|
||||
this.jwtSecret = config.jwtSecret;
|
||||
}
|
||||
|
||||
/**
|
||||
* EN: Verify JWT token and return user payload
|
||||
* VI: Xác minh JWT token và trả về thông tin người dùng
|
||||
*
|
||||
* @param token - JWT token to verify / JWT token cần xác minh
|
||||
* @returns User payload / Thông tin người dùng
|
||||
* @throws TokenExpiredError if token expired / Lỗi token hết hạn
|
||||
*/
|
||||
async verifyToken(token: string): Promise<UserPayload> {
|
||||
// Implementation
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Interface/Type Documentation
|
||||
```typescript
|
||||
/**
|
||||
* EN: User data transfer object
|
||||
* VI: Đối tượng truyền dữ liệu người dùng
|
||||
*/
|
||||
interface UserDto {
|
||||
/** EN: Unique user identifier / VI: Mã định danh duy nhất */
|
||||
id: string;
|
||||
|
||||
/** EN: User email address / VI: Địa chỉ email người dùng */
|
||||
email: string;
|
||||
|
||||
/** EN: User display name / VI: Tên hiển thị người dùng */
|
||||
name: string;
|
||||
|
||||
/** EN: User role for authorization / VI: Vai trò người dùng để phân quyền */
|
||||
role: 'admin' | 'user' | 'guest';
|
||||
}
|
||||
```
|
||||
|
||||
#### Complex Logic Comments
|
||||
```typescript
|
||||
async function processPayment(order: Order): Promise<PaymentResult> {
|
||||
// EN: Step 1: Validate order data
|
||||
// VI: Bước 1: Xác thực dữ liệu đơn hàng
|
||||
if (!order.items.length) {
|
||||
throw new Error('Order must have items / Đơn hàng phải có sản phẩm');
|
||||
}
|
||||
|
||||
// EN: Step 2: Calculate total amount
|
||||
// VI: Bước 2: Tính tổng số tiền
|
||||
const total = order.items.reduce((sum, item) => {
|
||||
return sum + (item.price * item.quantity);
|
||||
}, 0);
|
||||
|
||||
// EN: Step 3: Process payment through gateway
|
||||
// VI: Bước 3: Xử lý thanh toán qua cổng thanh toán
|
||||
try {
|
||||
const result = await paymentGateway.charge({
|
||||
amount: total,
|
||||
currency: 'VND',
|
||||
orderId: order.id,
|
||||
});
|
||||
|
||||
// EN: Step 4: Update order status on success
|
||||
// VI: Bước 4: Cập nhật trạng thái đơn hàng khi thành công
|
||||
await updateOrderStatus(order.id, 'paid');
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
// EN: Log error and mark order as failed
|
||||
// VI: Ghi log lỗi và đánh dấu đơn hàng thất bại
|
||||
logger.error('Payment failed', { orderId: order.id, error });
|
||||
await updateOrderStatus(order.id, 'failed');
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### React/Next.js Components
|
||||
|
||||
```typescript
|
||||
/**
|
||||
* EN: User profile card component
|
||||
* VI: Component thẻ hồ sơ người dùng
|
||||
*
|
||||
* @param user - User data to display / Dữ liệu người dùng để hiển thị
|
||||
* @param onEdit - Callback when edit button clicked / Callback khi nhấn nút chỉnh sửa
|
||||
*/
|
||||
export function UserCard({ user, onEdit }: UserCardProps) {
|
||||
// EN: Local state for loading status
|
||||
// VI: State cục bộ cho trạng thái loading
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
/**
|
||||
* EN: Handle user profile update
|
||||
* VI: Xử lý cập nhật hồ sơ người dùng
|
||||
*/
|
||||
const handleUpdate = async () => {
|
||||
setIsLoading(true);
|
||||
try {
|
||||
await updateUser(user.id, user);
|
||||
onEdit?.();
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="user-card">
|
||||
{/* EN: Display user avatar / VI: Hiển thị avatar người dùng */}
|
||||
<img src={user.avatar} alt={user.name} />
|
||||
|
||||
{/* EN: User information section / VI: Phần thông tin người dùng */}
|
||||
<div className="user-info">
|
||||
<h3>{user.name}</h3>
|
||||
<p>{user.email}</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### Prisma Schema
|
||||
|
||||
```prisma
|
||||
/// EN: User model for authentication and profile
|
||||
/// VI: Model người dùng cho xác thực và hồ sơ
|
||||
model User {
|
||||
/// EN: Unique identifier
|
||||
/// VI: Mã định danh duy nhất
|
||||
id String @id @default(cuid())
|
||||
|
||||
/// EN: User email (unique)
|
||||
/// VI: Email người dùng (duy nhất)
|
||||
email String @unique
|
||||
|
||||
/// EN: Hashed password
|
||||
/// VI: Mật khẩu đã mã hóa
|
||||
password String
|
||||
|
||||
/// EN: Display name
|
||||
/// VI: Tên hiển thị
|
||||
name String
|
||||
|
||||
/// EN: Account creation timestamp
|
||||
/// VI: Thời gian tạo tài khoản
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
/// EN: Last update timestamp
|
||||
/// VI: Thời gian cập nhật cuối
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@map("users")
|
||||
}
|
||||
```
|
||||
|
||||
### Configuration Files
|
||||
|
||||
```typescript
|
||||
// config/database.config.ts
|
||||
|
||||
/**
|
||||
* EN: Database configuration for Prisma and Neon PostgreSQL
|
||||
* VI: Cấu hình database cho Prisma và Neon PostgreSQL
|
||||
*/
|
||||
export const databaseConfig = {
|
||||
/**
|
||||
* EN: Database connection URL from environment
|
||||
* VI: URL kết nối database từ biến môi trường
|
||||
*/
|
||||
url: process.env.DATABASE_URL,
|
||||
|
||||
/**
|
||||
* EN: Connection pool settings
|
||||
* VI: Cài đặt connection pool
|
||||
*/
|
||||
pool: {
|
||||
// EN: Minimum connections in pool
|
||||
// VI: Số kết nối tối thiểu trong pool
|
||||
min: 2,
|
||||
|
||||
// EN: Maximum connections in pool
|
||||
// VI: Số kết nối tối đa trong pool
|
||||
max: 10,
|
||||
},
|
||||
|
||||
/**
|
||||
* EN: Enable query logging in development
|
||||
* VI: Bật ghi log truy vấn trong môi trường phát triển
|
||||
*/
|
||||
logging: process.env.NODE_ENV === 'development',
|
||||
};
|
||||
```
|
||||
|
||||
### API Routes/Controllers
|
||||
|
||||
```typescript
|
||||
/**
|
||||
* EN: User management controller
|
||||
* VI: Controller quản lý người dùng
|
||||
*/
|
||||
export class UserController {
|
||||
/**
|
||||
* EN: Get user by ID
|
||||
* VI: Lấy thông tin người dùng theo ID
|
||||
*
|
||||
* GET /api/users/:id
|
||||
*/
|
||||
async getById(req: Request, res: Response) {
|
||||
try {
|
||||
// EN: Extract user ID from params
|
||||
// VI: Lấy ID người dùng từ params
|
||||
const { id } = req.params;
|
||||
|
||||
// EN: Fetch user from database
|
||||
// VI: Lấy người dùng từ database
|
||||
const user = await this.userService.findById(id);
|
||||
|
||||
// EN: Return 404 if user not found
|
||||
// VI: Trả về 404 nếu không tìm thấy người dùng
|
||||
if (!user) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
error: {
|
||||
code: 'USER_NOT_FOUND',
|
||||
message: 'User not found / Không tìm thấy người dùng',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// EN: Return user data
|
||||
// VI: Trả về dữ liệu người dùng
|
||||
return res.json({
|
||||
success: true,
|
||||
data: user,
|
||||
});
|
||||
} catch (error) {
|
||||
// EN: Handle unexpected errors
|
||||
// VI: Xử lý lỗi không mong đợi
|
||||
logger.error('Failed to get user', { error, userId: req.params.id });
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
error: {
|
||||
code: 'INTERNAL_ERROR',
|
||||
message: 'Internal server error / Lỗi máy chủ nội bộ',
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Middleware
|
||||
|
||||
```typescript
|
||||
/**
|
||||
* EN: Authentication middleware to verify JWT tokens
|
||||
* VI: Middleware xác thực để kiểm tra JWT token
|
||||
*/
|
||||
export function authMiddleware(
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
) {
|
||||
// EN: Extract token from Authorization header
|
||||
// VI: Lấy token từ header Authorization
|
||||
const authHeader = req.headers.authorization;
|
||||
const token = authHeader?.replace('Bearer ', '');
|
||||
|
||||
// EN: Return 401 if no token provided
|
||||
// VI: Trả về 401 nếu không có token
|
||||
if (!token) {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
error: {
|
||||
code: 'NO_TOKEN',
|
||||
message: 'Authentication required / Yêu cầu xác thực',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
// EN: Verify token and extract payload
|
||||
// VI: Xác minh token và lấy payload
|
||||
const payload = jwt.verify(token, JWT_SECRET);
|
||||
|
||||
// EN: Attach user info to request
|
||||
// VI: Gắn thông tin người dùng vào request
|
||||
req.user = payload;
|
||||
|
||||
next();
|
||||
} catch (error) {
|
||||
// EN: Return 401 if token invalid or expired
|
||||
// VI: Trả về 401 nếu token không hợp lệ hoặc hết hạn
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
error: {
|
||||
code: 'INVALID_TOKEN',
|
||||
message: 'Invalid or expired token / Token không hợp lệ hoặc hết hạn',
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### 1. Comment Placement
|
||||
- Place bilingual comments together (EN first, then VI)
|
||||
- Keep comments close to the code they describe
|
||||
- Use JSDoc format for functions and classes
|
||||
|
||||
### 2. Comment Content
|
||||
- **DO**: Explain WHY, not WHAT (code shows what)
|
||||
- **DO**: Document complex logic and business rules
|
||||
- **DO**: Include parameter descriptions and return types
|
||||
- **DO**: Document error conditions and exceptions
|
||||
- **DON'T**: State the obvious
|
||||
- **DON'T**: Write redundant comments
|
||||
|
||||
### 3. Language Guidelines
|
||||
- **English**: Use clear, concise technical English
|
||||
- **Vietnamese**: Use proper Vietnamese technical terms
|
||||
- Keep translations accurate and natural
|
||||
- Use consistent terminology across codebase
|
||||
|
||||
### 4. Special Cases
|
||||
|
||||
#### TODO Comments
|
||||
```typescript
|
||||
// TODO EN: Implement caching for better performance
|
||||
// TODO VI: Triển khai caching để cải thiện hiệu suất
|
||||
```
|
||||
|
||||
#### FIXME Comments
|
||||
```typescript
|
||||
// FIXME EN: This causes memory leak, needs refactoring
|
||||
// FIXME VI: Đoạn này gây rò rỉ bộ nhớ, cần refactor
|
||||
```
|
||||
|
||||
#### WARNING Comments
|
||||
```typescript
|
||||
// WARNING EN: Do not modify this without updating the database schema
|
||||
// WARNING VI: Không sửa đổi phần này mà không cập nhật schema database
|
||||
```
|
||||
|
||||
### 5. Documentation Priority
|
||||
|
||||
**High Priority** (Always document):
|
||||
- Public APIs and exported functions
|
||||
- Complex algorithms and business logic
|
||||
- Security-critical code
|
||||
- Configuration and environment setup
|
||||
- Error handling strategies
|
||||
|
||||
**Medium Priority** (Document when helpful):
|
||||
- Helper functions with non-obvious behavior
|
||||
- Data transformations
|
||||
- Integration points with external services
|
||||
|
||||
**Low Priority** (Optional):
|
||||
- Simple getters/setters
|
||||
- Self-explanatory code
|
||||
- Standard CRUD operations
|
||||
|
||||
## Examples by Use Case
|
||||
|
||||
### Authentication Flow
|
||||
```typescript
|
||||
/**
|
||||
* EN: Complete authentication flow with refresh token
|
||||
* VI: Luồng xác thực hoàn chỉnh với refresh token
|
||||
*/
|
||||
export class AuthFlow {
|
||||
/**
|
||||
* EN: Login user and generate token pair
|
||||
* VI: Đăng nhập người dùng và tạo cặp token
|
||||
*/
|
||||
async login(credentials: LoginDto) {
|
||||
// EN: Step 1: Validate credentials
|
||||
// VI: Bước 1: Xác thực thông tin đăng nhập
|
||||
const user = await this.validateCredentials(credentials);
|
||||
|
||||
// EN: Step 2: Generate access token (15min expiry)
|
||||
// VI: Bước 2: Tạo access token (hết hạn sau 15 phút)
|
||||
const accessToken = this.generateAccessToken(user);
|
||||
|
||||
// EN: Step 3: Generate refresh token (7 days expiry)
|
||||
// VI: Bước 3: Tạo refresh token (hết hạn sau 7 ngày)
|
||||
const refreshToken = this.generateRefreshToken(user);
|
||||
|
||||
// EN: Step 4: Store refresh token in database
|
||||
// VI: Bước 4: Lưu refresh token vào database
|
||||
await this.storeRefreshToken(user.id, refreshToken);
|
||||
|
||||
return { accessToken, refreshToken };
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Database Transaction
|
||||
```typescript
|
||||
/**
|
||||
* EN: Transfer money between accounts with transaction
|
||||
* VI: Chuyển tiền giữa các tài khoản với transaction
|
||||
*/
|
||||
async function transferMoney(
|
||||
fromAccountId: string,
|
||||
toAccountId: string,
|
||||
amount: number
|
||||
) {
|
||||
// EN: Use transaction to ensure atomicity
|
||||
// VI: Sử dụng transaction để đảm bảo tính nguyên tử
|
||||
return await prisma.$transaction(async (tx) => {
|
||||
// EN: Deduct from sender account
|
||||
// VI: Trừ tiền từ tài khoản người gửi
|
||||
await tx.account.update({
|
||||
where: { id: fromAccountId },
|
||||
data: { balance: { decrement: amount } },
|
||||
});
|
||||
|
||||
// EN: Add to receiver account
|
||||
// VI: Cộng tiền vào tài khoản người nhận
|
||||
await tx.account.update({
|
||||
where: { id: toAccountId },
|
||||
data: { balance: { increment: amount } },
|
||||
});
|
||||
|
||||
// EN: Create transaction record
|
||||
// VI: Tạo bản ghi giao dịch
|
||||
return await tx.transaction.create({
|
||||
data: {
|
||||
fromAccountId,
|
||||
toAccountId,
|
||||
amount,
|
||||
type: 'TRANSFER',
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
## Integration with Project Rules
|
||||
|
||||
When commenting code in this project:
|
||||
- Follow the code organization from `project-rules` skill
|
||||
- Use consistent terminology with project documentation
|
||||
- Align with the API response format standards
|
||||
- Document according to the testing standards
|
||||
- Include security considerations where relevant
|
||||
|
||||
## Quick Reference
|
||||
|
||||
### Function Comment Template
|
||||
```typescript
|
||||
/**
|
||||
* EN: [Brief description in English]
|
||||
* VI: [Mô tả ngắn gọn bằng tiếng Việt]
|
||||
*
|
||||
* @param paramName - EN description / VI mô tả
|
||||
* @returns EN description / VI mô tả
|
||||
* @throws ErrorType EN when / VI khi nào
|
||||
*/
|
||||
```
|
||||
|
||||
### Inline Comment Template
|
||||
```typescript
|
||||
// EN: [English explanation]
|
||||
// VI: [Giải thích tiếng Việt]
|
||||
```
|
||||
|
||||
### Complex Block Template
|
||||
```typescript
|
||||
// EN: Step N: [What this block does]
|
||||
// VI: Bước N: [Block này làm gì]
|
||||
```
|
||||
476
.cursor/skills/project-rules/SKILL.md
Normal file
476
.cursor/skills/project-rules/SKILL.md
Normal file
@@ -0,0 +1,476 @@
|
||||
---
|
||||
name: project-rules
|
||||
description: GoodGo Microservices Platform coding standards, architecture patterns, and development guidelines. Use when working with this monorepo to ensure code follows project conventions for services, apps, packages, infrastructure, or when making architectural decisions.
|
||||
---
|
||||
|
||||
# GoodGo Project Rules
|
||||
|
||||
## Architecture Overview
|
||||
|
||||
This is an enterprise-grade microservices monorepo with:
|
||||
- **Apps**: Next.js (web-admin, web-client) + Flutter (app-admin, app-client)
|
||||
- **Services**: Node.js/TypeScript microservices with Express
|
||||
- **Packages**: Shared libraries (logger, types, http-client, auth-sdk, tracing, config)
|
||||
- **Infrastructure**: Traefik, Redis, Neon PostgreSQL, Observability stack
|
||||
- **Deployments**: Local (Docker Compose), Staging/Production (Kubernetes)
|
||||
|
||||
## Tech Stack Requirements
|
||||
|
||||
### Frontend
|
||||
- **Web**: Next.js 14+ with App Router, TypeScript, Tailwind CSS, Zustand
|
||||
- **Mobile**: Flutter 3.x with Provider pattern
|
||||
- Use shared types from `@goodgo/types` package
|
||||
- API calls via `@goodgo/http-client`
|
||||
|
||||
### Backend Services
|
||||
- **Runtime**: Node.js 20+, TypeScript 5+
|
||||
- **Framework**: Express with modular architecture
|
||||
- **Database**: Prisma ORM with Neon PostgreSQL
|
||||
- **Validation**: Zod for DTOs
|
||||
- **Logging**: Use `@goodgo/logger` package
|
||||
- **Tracing**: Use `@goodgo/tracing` with OpenTelemetry
|
||||
- **Auth**: JWT tokens, use `@goodgo/auth-sdk`
|
||||
|
||||
### Infrastructure
|
||||
- **API Gateway**: Traefik with path-based routing
|
||||
- **Caching**: Redis for sessions/cache
|
||||
- **Monitoring**: Prometheus + Grafana + Loki
|
||||
- **Containerization**: Docker with multi-stage builds
|
||||
|
||||
## Code Organization Standards
|
||||
|
||||
### Service Structure
|
||||
```
|
||||
services/service-name/
|
||||
├── src/
|
||||
│ ├── config/ # Configuration files
|
||||
│ ├── modules/ # Feature modules
|
||||
│ │ └── feature/
|
||||
│ │ ├── feature.controller.ts
|
||||
│ │ ├── feature.service.ts
|
||||
│ │ ├── feature.dto.ts
|
||||
│ │ └── feature.module.ts
|
||||
│ ├── middlewares/ # Express middlewares
|
||||
│ ├── routes/ # Route definitions
|
||||
│ └── main.ts # Entry point
|
||||
├── prisma/
|
||||
│ ├── schema.prisma
|
||||
│ └── seed.ts
|
||||
├── Dockerfile
|
||||
├── package.json
|
||||
└── tsconfig.json
|
||||
```
|
||||
|
||||
### Package Structure
|
||||
```
|
||||
packages/package-name/
|
||||
├── src/
|
||||
│ └── index.ts # Main export
|
||||
├── package.json
|
||||
├── tsconfig.json
|
||||
└── README.md
|
||||
```
|
||||
|
||||
### App Structure
|
||||
```
|
||||
apps/web-*/
|
||||
├── src/
|
||||
│ ├── app/ # Next.js App Router
|
||||
│ ├── services/api/ # API clients
|
||||
│ └── stores/ # Zustand stores
|
||||
├── Dockerfile
|
||||
└── package.json
|
||||
```
|
||||
|
||||
## Naming Conventions
|
||||
|
||||
### Files & Folders
|
||||
- **Services**: `kebab-case` (e.g., `auth-service`, `user-service`)
|
||||
- **Packages**: `kebab-case` (e.g., `http-client`, `auth-sdk`)
|
||||
- **Files**: `kebab-case.type.ts` (e.g., `user.controller.ts`, `auth.service.ts`)
|
||||
- **Components**: `PascalCase.tsx` for React, `snake_case.dart` for Flutter
|
||||
|
||||
### Code
|
||||
- **Classes**: `PascalCase` (e.g., `UserService`, `AuthController`)
|
||||
- **Functions/Methods**: `camelCase` (e.g., `getUserById`, `validateToken`)
|
||||
- **Constants**: `UPPER_SNAKE_CASE` (e.g., `JWT_SECRET`, `API_VERSION`)
|
||||
- **Interfaces/Types**: `PascalCase` with descriptive names (e.g., `UserResponse`, `LoginDto`)
|
||||
|
||||
### Package Names
|
||||
- Use `@goodgo/` scope for all packages
|
||||
- Format: `@goodgo/package-name`
|
||||
|
||||
## Development Workflow
|
||||
|
||||
### Creating New Service
|
||||
1. Copy `services/_template/` as starting point
|
||||
2. Update `package.json` with correct name: `@goodgo/service-name`
|
||||
3. Add Prisma schema if database needed
|
||||
4. Create modules following modular pattern
|
||||
5. Add health check endpoint
|
||||
6. Create Dockerfile with multi-stage build
|
||||
7. Add to `turbo.json` if needed
|
||||
8. Update documentation
|
||||
|
||||
### Creating New Package
|
||||
1. Create in `packages/` directory
|
||||
2. Use TypeScript with strict mode
|
||||
3. Export from `src/index.ts`
|
||||
4. Add to `pnpm-workspace.yaml`
|
||||
5. Document usage in README.md
|
||||
6. Version with semantic versioning
|
||||
|
||||
### Adding Dependencies
|
||||
```bash
|
||||
# Service/App dependency
|
||||
pnpm --filter @goodgo/service-name add package-name
|
||||
|
||||
# Workspace package dependency
|
||||
pnpm --filter @goodgo/service-name add @goodgo/logger
|
||||
|
||||
# Dev dependency
|
||||
pnpm --filter @goodgo/service-name add -D @types/package-name
|
||||
|
||||
# Root dependency
|
||||
pnpm add -w package-name
|
||||
```
|
||||
|
||||
### Database Workflow
|
||||
1. Update Prisma schema in service
|
||||
2. Generate migration: `pnpm --filter @goodgo/service-name prisma migrate dev`
|
||||
3. Generate client: `pnpm --filter @goodgo/service-name prisma generate`
|
||||
4. Use Neon PostgreSQL (no local PostgreSQL needed)
|
||||
5. Connection pooling via Prisma
|
||||
|
||||
## Code Quality Standards
|
||||
|
||||
### TypeScript
|
||||
- Enable strict mode
|
||||
- No `any` types (use `unknown` if needed)
|
||||
- Define interfaces for all DTOs
|
||||
- Use Zod for runtime validation
|
||||
- Export types from `@goodgo/types` when shared
|
||||
|
||||
### Error Handling
|
||||
- Use custom error classes
|
||||
- Implement global error middleware
|
||||
- Log errors with context using `@goodgo/logger`
|
||||
- Return consistent error responses:
|
||||
```typescript
|
||||
{
|
||||
success: false,
|
||||
error: {
|
||||
code: 'ERROR_CODE',
|
||||
message: 'Human readable message',
|
||||
details?: any
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### API Response Format
|
||||
```typescript
|
||||
// Success
|
||||
{
|
||||
success: true,
|
||||
data: any
|
||||
}
|
||||
|
||||
// Error
|
||||
{
|
||||
success: false,
|
||||
error: {
|
||||
code: string,
|
||||
message: string,
|
||||
details?: any
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Logging
|
||||
```typescript
|
||||
import { logger } from '@goodgo/logger';
|
||||
|
||||
logger.info('Message', { context: 'data' });
|
||||
logger.error('Error', { error, context: 'data' });
|
||||
logger.debug('Debug info', { data });
|
||||
```
|
||||
|
||||
### Environment Variables
|
||||
- Use `.env.example` as template
|
||||
- Never commit `.env` files
|
||||
- Validate env vars at startup
|
||||
- Use Zod for env validation
|
||||
- Document all env vars in README
|
||||
|
||||
## Testing Standards
|
||||
|
||||
### Unit Tests
|
||||
- Place tests next to source: `feature.service.test.ts`
|
||||
- Use Jest as test runner
|
||||
- Mock external dependencies
|
||||
- Aim for >80% coverage
|
||||
|
||||
### Integration Tests
|
||||
- Test API endpoints end-to-end
|
||||
- Use test database (Neon branch)
|
||||
- Clean up test data after tests
|
||||
|
||||
### Test Commands
|
||||
```bash
|
||||
pnpm test # All tests
|
||||
pnpm --filter @goodgo/service-name test # Service tests
|
||||
pnpm test:coverage # With coverage
|
||||
```
|
||||
|
||||
## Docker Standards
|
||||
|
||||
### Multi-stage Builds
|
||||
```dockerfile
|
||||
# Build stage
|
||||
FROM node:20-alpine AS builder
|
||||
WORKDIR /app
|
||||
COPY package*.json pnpm-lock.yaml ./
|
||||
RUN npm install -g pnpm && pnpm install
|
||||
COPY . .
|
||||
RUN pnpm build
|
||||
|
||||
# Production stage
|
||||
FROM node:20-alpine
|
||||
WORKDIR /app
|
||||
COPY --from=builder /app/dist ./dist
|
||||
COPY --from=builder /app/node_modules ./node_modules
|
||||
CMD ["node", "dist/main.js"]
|
||||
```
|
||||
|
||||
### Image Naming
|
||||
- Format: `goodgo/service-name:version`
|
||||
- Use semantic versioning
|
||||
- Tag latest for production
|
||||
|
||||
## Git Workflow
|
||||
|
||||
### Branch Naming
|
||||
- Feature: `feature/description`
|
||||
- Bug fix: `fix/description`
|
||||
- Hotfix: `hotfix/description`
|
||||
- Release: `release/version`
|
||||
|
||||
### Commit Messages
|
||||
Follow Conventional Commits:
|
||||
```
|
||||
type(scope): subject
|
||||
|
||||
body (optional)
|
||||
|
||||
footer (optional)
|
||||
```
|
||||
|
||||
Types: `feat`, `fix`, `docs`, `style`, `refactor`, `test`, `chore`
|
||||
|
||||
Examples:
|
||||
- `feat(auth): add refresh token endpoint`
|
||||
- `fix(user): resolve email validation bug`
|
||||
- `docs(readme): update installation steps`
|
||||
|
||||
### Pull Requests
|
||||
- Use PR template
|
||||
- Link related issues
|
||||
- Request review from team
|
||||
- Ensure CI passes
|
||||
- Squash merge to main
|
||||
|
||||
## CI/CD Standards
|
||||
|
||||
### GitHub Actions
|
||||
- Run on PR: lint, test, build
|
||||
- Deploy to staging on merge to `develop`
|
||||
- Deploy to production on merge to `main`
|
||||
- Use secrets for sensitive data
|
||||
|
||||
### Deployment Checklist
|
||||
- [ ] All tests pass
|
||||
- [ ] No linter errors
|
||||
- [ ] Environment variables configured
|
||||
- [ ] Database migrations applied
|
||||
- [ ] Documentation updated
|
||||
- [ ] Monitoring configured
|
||||
|
||||
## Security Guidelines
|
||||
|
||||
### Authentication
|
||||
- Use JWT with short expiry (15min access, 7d refresh)
|
||||
- Store tokens securely (httpOnly cookies for web)
|
||||
- Implement refresh token rotation
|
||||
- Use `@goodgo/auth-sdk` for consistency
|
||||
|
||||
### Authorization
|
||||
- Implement RBAC (Role-Based Access Control)
|
||||
- Check permissions at service level
|
||||
- Use middleware for route protection
|
||||
|
||||
### Data Protection
|
||||
- Hash passwords with bcrypt (cost factor 12)
|
||||
- Encrypt sensitive data at rest
|
||||
- Use HTTPS in production
|
||||
- Sanitize user inputs
|
||||
- Validate all DTOs with Zod
|
||||
|
||||
### Secrets Management
|
||||
- Use environment variables
|
||||
- Kubernetes secrets for production
|
||||
- GitHub secrets for CI/CD
|
||||
- Never hardcode secrets
|
||||
- Rotate secrets regularly
|
||||
|
||||
## Performance Guidelines
|
||||
|
||||
### Backend
|
||||
- Use Redis for caching
|
||||
- Implement database connection pooling
|
||||
- Add pagination for list endpoints
|
||||
- Use database indexes
|
||||
- Implement rate limiting
|
||||
|
||||
### Frontend
|
||||
- Use Next.js Image optimization
|
||||
- Implement code splitting
|
||||
- Lazy load components
|
||||
- Use React.memo for expensive renders
|
||||
- Optimize bundle size
|
||||
|
||||
### Database
|
||||
- Use Prisma query optimization
|
||||
- Add indexes for frequent queries
|
||||
- Use database transactions
|
||||
- Implement soft deletes
|
||||
- Regular backups (Neon handles this)
|
||||
|
||||
## Monitoring & Observability
|
||||
|
||||
### Metrics
|
||||
- Use Prometheus for metrics
|
||||
- Track: request count, duration, errors
|
||||
- Set up alerts for critical metrics
|
||||
|
||||
### Logging
|
||||
- Structured logging with `@goodgo/logger`
|
||||
- Include trace IDs for correlation
|
||||
- Log levels: error, warn, info, debug
|
||||
- Aggregate logs with Loki
|
||||
|
||||
### Tracing
|
||||
- Use OpenTelemetry via `@goodgo/tracing`
|
||||
- Trace cross-service requests
|
||||
- Include context in traces
|
||||
|
||||
## Documentation Standards
|
||||
|
||||
### Code Documentation
|
||||
- JSDoc for public APIs
|
||||
- Inline comments for complex logic
|
||||
- README.md for each service/package
|
||||
- Keep docs up to date
|
||||
|
||||
### API Documentation
|
||||
- OpenAPI/Swagger specs in `docs/api/openapi/`
|
||||
- Document all endpoints
|
||||
- Include request/response examples
|
||||
- Document error codes
|
||||
|
||||
### Architecture Documentation
|
||||
- System design in `docs/architecture/`
|
||||
- Service communication patterns
|
||||
- Data flow diagrams
|
||||
- Decision records (ADRs)
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### Module Pattern
|
||||
```typescript
|
||||
// feature.module.ts
|
||||
export class FeatureModule {
|
||||
controller: FeatureController;
|
||||
service: FeatureService;
|
||||
|
||||
constructor() {
|
||||
this.service = new FeatureService();
|
||||
this.controller = new FeatureController(this.service);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### DTO Pattern
|
||||
```typescript
|
||||
// feature.dto.ts
|
||||
import { z } from 'zod';
|
||||
|
||||
export const CreateFeatureDto = z.object({
|
||||
name: z.string().min(1),
|
||||
email: z.string().email(),
|
||||
});
|
||||
|
||||
export type CreateFeatureDto = z.infer<typeof CreateFeatureDto>;
|
||||
```
|
||||
|
||||
### Controller Pattern
|
||||
```typescript
|
||||
// feature.controller.ts
|
||||
export class FeatureController {
|
||||
constructor(private service: FeatureService) {}
|
||||
|
||||
async create(req: Request, res: Response) {
|
||||
try {
|
||||
const dto = CreateFeatureDto.parse(req.body);
|
||||
const result = await this.service.create(dto);
|
||||
res.json({ success: true, data: result });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Service Pattern
|
||||
```typescript
|
||||
// feature.service.ts
|
||||
export class FeatureService {
|
||||
constructor(private prisma: PrismaClient) {}
|
||||
|
||||
async create(dto: CreateFeatureDto) {
|
||||
return this.prisma.feature.create({ data: dto });
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
- **Port conflicts**: Check `deployments/local/docker-compose.yml`
|
||||
- **Database connection**: Verify Neon DATABASE_URL in `.env.local`
|
||||
- **Module not found**: Run `pnpm install` in root
|
||||
- **Build errors**: Clear `dist/` and rebuild
|
||||
- **Type errors**: Run `pnpm --filter @goodgo/service-name prisma generate`
|
||||
|
||||
### Debug Commands
|
||||
```bash
|
||||
# Check service logs
|
||||
docker-compose logs -f service-name
|
||||
|
||||
# Check database connection
|
||||
pnpm --filter @goodgo/service-name prisma studio
|
||||
|
||||
# Rebuild containers
|
||||
docker-compose up -d --build
|
||||
|
||||
# Check running services
|
||||
docker-compose ps
|
||||
```
|
||||
|
||||
## Resources
|
||||
|
||||
- [Architecture Docs](../../docs/architecture/)
|
||||
- [API Specs](../../docs/api/openapi/)
|
||||
- [Development Guide](../../docs/guides/development.md)
|
||||
- [Deployment Guide](../../docs/guides/deployment.md)
|
||||
- [Neon Database Guide](../../docs/guides/neon-database.md)
|
||||
- [Contributing Guide](../../CONTRIBUTING.md)
|
||||
73
.github/workflows/ci-auth-service.yml
vendored
Normal file
73
.github/workflows/ci-auth-service.yml
vendored
Normal file
@@ -0,0 +1,73 @@
|
||||
name: Auth Service CI
|
||||
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- 'services/auth-service/**'
|
||||
- 'packages/**'
|
||||
pull_request:
|
||||
paths:
|
||||
- 'services/auth-service/**'
|
||||
- 'packages/**'
|
||||
|
||||
jobs:
|
||||
lint-and-test:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
# Use Neon test database if available, otherwise use PostgreSQL service
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:16-alpine
|
||||
env:
|
||||
POSTGRES_USER: testuser
|
||||
POSTGRES_PASSWORD: testpass
|
||||
POSTGRES_DB: test_db
|
||||
options: >-
|
||||
--health-cmd pg_isready
|
||||
--health-interval 10s
|
||||
--health-timeout 5s
|
||||
--health-retries 5
|
||||
ports:
|
||||
- 5432:5432
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup PNPM
|
||||
uses: pnpm/action-setup@v2
|
||||
with:
|
||||
version: 8
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Generate Prisma Client
|
||||
run: pnpm --filter @goodgo/auth-service prisma:generate
|
||||
env:
|
||||
# Use Neon test DB if available, otherwise fallback to local PostgreSQL
|
||||
DATABASE_URL: ${{ secrets.NEON_DATABASE_URL_TEST != '' && secrets.NEON_DATABASE_URL_TEST || 'postgresql://testuser:testpass@localhost:5432/test_db' }}
|
||||
|
||||
- name: Run migrations
|
||||
run: pnpm --filter @goodgo/auth-service prisma migrate deploy
|
||||
env:
|
||||
DATABASE_URL: ${{ secrets.NEON_DATABASE_URL_TEST != '' && secrets.NEON_DATABASE_URL_TEST || 'postgresql://testuser:testpass@localhost:5432/test_db' }}
|
||||
|
||||
- name: Lint
|
||||
run: pnpm --filter @goodgo/auth-service lint
|
||||
|
||||
- name: Type check
|
||||
run: pnpm --filter @goodgo/auth-service typecheck
|
||||
|
||||
- name: Build
|
||||
run: pnpm --filter @goodgo/auth-service build
|
||||
|
||||
- name: Test
|
||||
run: pnpm --filter @goodgo/auth-service test
|
||||
env:
|
||||
DATABASE_URL: ${{ secrets.NEON_DATABASE_URL_TEST != '' && secrets.NEON_DATABASE_URL_TEST || 'postgresql://testuser:testpass@localhost:5432/test_db' }}
|
||||
57
.github/workflows/ci-mobile.yml
vendored
Normal file
57
.github/workflows/ci-mobile.yml
vendored
Normal file
@@ -0,0 +1,57 @@
|
||||
name: Mobile Apps CI
|
||||
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- 'apps/app-*/**'
|
||||
- 'packages/**'
|
||||
pull_request:
|
||||
paths:
|
||||
- 'apps/app-*/**'
|
||||
- 'packages/**'
|
||||
|
||||
jobs:
|
||||
lint-and-test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup PNPM
|
||||
uses: pnpm/action-setup@v2
|
||||
with:
|
||||
version: 8
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Setup Flutter
|
||||
uses: subosito/flutter-action@v2
|
||||
with:
|
||||
flutter-version: '3.16.0'
|
||||
channel: 'stable'
|
||||
|
||||
- name: Install Flutter dependencies (Admin)
|
||||
run: |
|
||||
cd apps/app-admin
|
||||
flutter pub get
|
||||
|
||||
- name: Install Flutter dependencies (Client)
|
||||
run: |
|
||||
cd apps/app-client
|
||||
flutter pub get
|
||||
|
||||
- name: Analyze Flutter code (Admin)
|
||||
run: |
|
||||
cd apps/app-admin
|
||||
flutter analyze
|
||||
|
||||
- name: Analyze Flutter code (Client)
|
||||
run: |
|
||||
cd apps/app-client
|
||||
flutter analyze
|
||||
49
.github/workflows/ci-web.yml
vendored
Normal file
49
.github/workflows/ci-web.yml
vendored
Normal file
@@ -0,0 +1,49 @@
|
||||
name: Web Apps CI
|
||||
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- 'apps/web-*/**'
|
||||
- 'packages/**'
|
||||
pull_request:
|
||||
paths:
|
||||
- 'apps/web-*/**'
|
||||
- 'packages/**'
|
||||
|
||||
jobs:
|
||||
lint-and-test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup PNPM
|
||||
uses: pnpm/action-setup@v2
|
||||
with:
|
||||
version: 8
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Lint Web Admin
|
||||
run: pnpm --filter @goodgo/web-admin lint || echo "Skipped"
|
||||
|
||||
- name: Lint Web Client
|
||||
run: pnpm --filter @goodgo/web-client lint || echo "Skipped"
|
||||
|
||||
- name: Type check Web Admin
|
||||
run: pnpm --filter @goodgo/web-admin typecheck || echo "Skipped"
|
||||
|
||||
- name: Type check Web Client
|
||||
run: pnpm --filter @goodgo/web-client typecheck || echo "Skipped"
|
||||
|
||||
- name: Build Web Admin
|
||||
run: pnpm --filter @goodgo/web-admin build || echo "Skipped"
|
||||
|
||||
- name: Build Web Client
|
||||
run: pnpm --filter @goodgo/web-client build || echo "Skipped"
|
||||
58
.github/workflows/deploy-production.yml
vendored
Normal file
58
.github/workflows/deploy-production.yml
vendored
Normal file
@@ -0,0 +1,58 @@
|
||||
name: Deploy to Production
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
environment: production
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup PNPM
|
||||
uses: pnpm/action-setup@v2
|
||||
with:
|
||||
version: 8
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Run database migrations
|
||||
run: |
|
||||
cd services/auth-service
|
||||
pnpm prisma generate
|
||||
pnpm prisma migrate deploy
|
||||
env:
|
||||
DATABASE_URL: ${{ secrets.NEON_DATABASE_URL_PRODUCTION }}
|
||||
|
||||
- name: Setup kubectl
|
||||
uses: azure/setup-kubectl@v3
|
||||
|
||||
- name: Configure kubectl
|
||||
run: |
|
||||
echo "${{ secrets.KUBECONFIG_PRODUCTION }}" | base64 -d > kubeconfig
|
||||
export KUBECONFIG=./kubeconfig
|
||||
|
||||
- name: Deploy Auth Service
|
||||
run: |
|
||||
export KUBECONFIG=./kubeconfig
|
||||
kubectl apply -f deployments/production/kubernetes/auth-service.yaml
|
||||
kubectl apply -f deployments/production/kubernetes/configmap.yaml
|
||||
kubectl apply -f deployments/production/kubernetes/ingress.yaml
|
||||
kubectl rollout status deployment/auth-service -n production
|
||||
|
||||
- name: Deploy Web App
|
||||
run: |
|
||||
export KUBECONFIG=./kubeconfig
|
||||
kubectl apply -f deployments/production/kubernetes/web-app.yaml || echo "Web app deployment not configured"
|
||||
kubectl rollout status deployment/web-app -n production || echo "Web app deployment not configured"
|
||||
57
.github/workflows/deploy-staging.yml
vendored
Normal file
57
.github/workflows/deploy-staging.yml
vendored
Normal file
@@ -0,0 +1,57 @@
|
||||
name: Deploy to Staging
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- develop
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup PNPM
|
||||
uses: pnpm/action-setup@v2
|
||||
with:
|
||||
version: 8
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Run database migrations
|
||||
run: |
|
||||
cd services/auth-service
|
||||
pnpm prisma generate
|
||||
pnpm prisma migrate deploy
|
||||
env:
|
||||
DATABASE_URL: ${{ secrets.NEON_DATABASE_URL_STAGING }}
|
||||
|
||||
- name: Setup kubectl
|
||||
uses: azure/setup-kubectl@v3
|
||||
|
||||
- name: Configure kubectl
|
||||
run: |
|
||||
echo "${{ secrets.KUBECONFIG_STAGING }}" | base64 -d > kubeconfig
|
||||
export KUBECONFIG=./kubeconfig
|
||||
|
||||
- name: Deploy Auth Service
|
||||
run: |
|
||||
export KUBECONFIG=./kubeconfig
|
||||
kubectl apply -f deployments/staging/kubernetes/auth-service.yaml
|
||||
kubectl apply -f deployments/staging/kubernetes/configmap.yaml
|
||||
kubectl apply -f deployments/staging/kubernetes/ingress.yaml
|
||||
kubectl rollout status deployment/auth-service -n staging
|
||||
|
||||
- name: Deploy Web App
|
||||
run: |
|
||||
export KUBECONFIG=./kubeconfig
|
||||
kubectl apply -f deployments/staging/kubernetes/web-app.yaml || echo "Web app deployment not configured"
|
||||
kubectl rollout status deployment/web-app -n staging || echo "Web app deployment not configured"
|
||||
87
.github/workflows/docker-build.yml
vendored
Normal file
87
.github/workflows/docker-build.yml
vendored
Normal file
@@ -0,0 +1,87 @@
|
||||
name: Docker Build
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- develop
|
||||
paths:
|
||||
- 'services/auth-service/**'
|
||||
- 'apps/web-*/**'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build-auth-service:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
|
||||
- name: Build and push Auth Service
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: ./services/auth-service
|
||||
push: true
|
||||
tags: |
|
||||
goodgo/auth-service:latest
|
||||
goodgo/auth-service:${{ github.sha }}
|
||||
cache-from: type=registry,ref=goodgo/auth-service:buildcache
|
||||
cache-to: type=registry,ref=goodgo/auth-service:buildcache,mode=max
|
||||
|
||||
build-web-admin:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
|
||||
- name: Build and push Web Admin
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: ./apps/web-admin
|
||||
push: true
|
||||
tags: |
|
||||
goodgo/web-admin:latest
|
||||
goodgo/web-admin:${{ github.sha }}
|
||||
cache-from: type=registry,ref=goodgo/web-admin:buildcache
|
||||
cache-to: type=registry,ref=goodgo/web-admin:buildcache,mode=max
|
||||
|
||||
build-web-client:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
|
||||
- name: Build and push Web Client
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: ./apps/web-client
|
||||
push: true
|
||||
tags: |
|
||||
goodgo/web-client:latest
|
||||
goodgo/web-client:${{ github.sha }}
|
||||
cache-from: type=registry,ref=goodgo/web-client:buildcache
|
||||
cache-to: type=registry,ref=goodgo/web-client:buildcache,mode=max
|
||||
34
.github/workflows/pr-checks.yml
vendored
Normal file
34
.github/workflows/pr-checks.yml
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
name: PR Checks
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened]
|
||||
|
||||
jobs:
|
||||
check:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup PNPM
|
||||
uses: pnpm/action-setup@v2
|
||||
with:
|
||||
version: 8
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
cache: 'pnpm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Lint all
|
||||
run: pnpm lint
|
||||
|
||||
- name: Type check all
|
||||
run: pnpm typecheck
|
||||
|
||||
- name: Build all
|
||||
run: pnpm build
|
||||
82
.gitignore
vendored
Normal file
82
.gitignore
vendored
Normal file
@@ -0,0 +1,82 @@
|
||||
# Dependencies
|
||||
node_modules/
|
||||
.pnp
|
||||
.pnp.js
|
||||
|
||||
# Testing
|
||||
coverage/
|
||||
*.lcov
|
||||
.nyc_output
|
||||
|
||||
# Production
|
||||
dist/
|
||||
build/
|
||||
.next/
|
||||
out/
|
||||
|
||||
# Environment variables
|
||||
.env
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
.env*.local
|
||||
|
||||
# Logs
|
||||
logs/
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
.DS_Store?
|
||||
._*
|
||||
.Spotlight-V100
|
||||
.Trashes
|
||||
ehthumbs.db
|
||||
Thumbs.db
|
||||
|
||||
# IDE
|
||||
.vscode/
|
||||
.idea/
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
.project
|
||||
.classpath
|
||||
.settings/
|
||||
|
||||
# Prisma
|
||||
prisma/migrations/*.sql
|
||||
|
||||
# Docker
|
||||
.dockerignore
|
||||
|
||||
# Secrets
|
||||
infra/secrets/**/*
|
||||
!infra/secrets/**/.env.example
|
||||
!infra/secrets/**/.gitignore
|
||||
|
||||
# Temporary files
|
||||
*.tmp
|
||||
*.temp
|
||||
.cache/
|
||||
.turbo/
|
||||
|
||||
# Database
|
||||
*.db
|
||||
*.sqlite
|
||||
*.sqlite3
|
||||
|
||||
# Certificates
|
||||
*.pem
|
||||
*.key
|
||||
*.crt
|
||||
infra/traefik/certs/*
|
||||
|
||||
# Build artifacts
|
||||
*.tsbuildinfo
|
||||
93
CONTRIBUTING.md
Normal file
93
CONTRIBUTING.md
Normal file
@@ -0,0 +1,93 @@
|
||||
# Contributing Guide
|
||||
|
||||
Thank you for considering contributing to GoodGo Microservices Platform!
|
||||
|
||||
## Development Workflow
|
||||
|
||||
### Branch Strategy
|
||||
|
||||
We follow Git Flow:
|
||||
|
||||
- `main` - Production-ready code
|
||||
- `develop` - Integration branch for features
|
||||
- `feature/*` - New features
|
||||
- `bugfix/*` - Bug fixes
|
||||
- `hotfix/*` - Critical production fixes
|
||||
|
||||
### Commit Convention
|
||||
|
||||
We use [Conventional Commits](https://www.conventionalcommits.org/):
|
||||
|
||||
```bash
|
||||
feat: add login endpoint
|
||||
fix: resolve token expiration issue
|
||||
docs: update API documentation
|
||||
refactor: restructure auth module
|
||||
test: add unit tests for auth service
|
||||
chore: update dependencies
|
||||
ci: add GitHub Actions workflow
|
||||
perf: optimize database queries
|
||||
```
|
||||
|
||||
### Pull Request Process
|
||||
|
||||
1. Create feature branch from `develop`
|
||||
2. Implement changes with tests
|
||||
3. Run linter and tests locally
|
||||
4. Push and create PR
|
||||
5. CI/CD will auto-run checks
|
||||
6. Code review required
|
||||
7. Merge into `develop` (auto-deploy to staging)
|
||||
8. QA testing on staging
|
||||
9. Merge into `main` (manual deploy to production)
|
||||
|
||||
### Code Standards
|
||||
|
||||
- **TypeScript**: Strict mode enabled
|
||||
- **Linting**: ESLint with shared config
|
||||
- **Formatting**: Prettier
|
||||
- **Testing**: Minimum 80% coverage
|
||||
- **Documentation**: JSDoc for public APIs
|
||||
|
||||
### Running Tests
|
||||
|
||||
```bash
|
||||
# All tests
|
||||
pnpm test
|
||||
|
||||
# Specific package
|
||||
pnpm --filter @goodgo/auth-service test
|
||||
|
||||
# With coverage
|
||||
pnpm --filter @goodgo/auth-service test:coverage
|
||||
```
|
||||
|
||||
### Code Review Checklist
|
||||
|
||||
- [ ] Code follows style guidelines
|
||||
- [ ] Tests added/updated
|
||||
- [ ] Documentation updated
|
||||
- [ ] No console.logs or debug code
|
||||
- [ ] Environment variables documented
|
||||
- [ ] Breaking changes documented
|
||||
|
||||
## Adding a New Service
|
||||
|
||||
1. Copy `services/_template` to `services/new-service`
|
||||
2. Update package.json name and dependencies
|
||||
3. Implement service logic
|
||||
4. Add tests
|
||||
5. Update documentation
|
||||
6. Create CI/CD workflow
|
||||
|
||||
## Adding a New Package
|
||||
|
||||
1. Create package in `packages/new-package`
|
||||
2. Add to workspace
|
||||
3. Export from index.ts
|
||||
4. Add tests
|
||||
5. Document usage
|
||||
|
||||
## Questions?
|
||||
|
||||
Feel free to open an issue or contact the team.
|
||||
21
LICENSE
Normal file
21
LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2024 GoodGo
|
||||
|
||||
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.
|
||||
200
README.md
Normal file
200
README.md
Normal file
@@ -0,0 +1,200 @@
|
||||
# GoodGo Microservices Platform
|
||||
|
||||
Enterprise-grade microservices monorepo built with modern technologies and best practices.
|
||||
|
||||
## 🏗️ Architecture
|
||||
|
||||
This monorepo follows a microservices architecture pattern with:
|
||||
|
||||
- **Apps**: Frontend applications (Web + Mobile)
|
||||
- **Services**: Backend microservices (starting with Auth Service)
|
||||
- **Packages**: Shared libraries and utilities
|
||||
- **Infrastructure**: Traefik, Observability, Databases
|
||||
- **Deployments**: Environment-specific configurations
|
||||
|
||||
## 📁 Project Structure
|
||||
|
||||
```
|
||||
├── apps/ # Frontend applications
|
||||
│ ├── web-admin/ # Next.js admin web application
|
||||
│ ├── web-client/ # Next.js client web application
|
||||
│ ├── app-admin/ # Flutter admin mobile application
|
||||
│ └── app-client/ # Flutter client mobile application
|
||||
├── services/ # Backend microservices
|
||||
│ ├── auth-service/ # Authentication service
|
||||
│ └── _template/ # Service template
|
||||
├── packages/ # Shared libraries
|
||||
│ ├── logger/ # Centralized logging
|
||||
│ ├── types/ # TypeScript types
|
||||
│ ├── config/ # Shared configs
|
||||
│ ├── http-client/ # API client wrapper
|
||||
│ ├── auth-sdk/ # Auth utilities
|
||||
│ └── tracing/ # OpenTelemetry setup
|
||||
├── infra/ # Infrastructure as Code
|
||||
│ ├── traefik/ # Traefik configuration
|
||||
│ ├── docker/ # Docker configs
|
||||
│ ├── observability/ # Monitoring stack
|
||||
│ └── databases/ # Database configs
|
||||
├── deployments/ # Environment configs
|
||||
│ ├── local/ # Local development
|
||||
│ ├── staging/ # Staging environment
|
||||
│ └── production/ # Production environment
|
||||
├── .github/ # CI/CD workflows
|
||||
├── scripts/ # Automation scripts
|
||||
└── docs/ # Documentation
|
||||
```
|
||||
|
||||
## 🚀 Getting Started
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- Node.js >= 20.0.0
|
||||
- pnpm >= 8.0.0
|
||||
- Docker & Docker Compose
|
||||
- Neon account (https://neon.tech) - for PostgreSQL database
|
||||
|
||||
### Installation
|
||||
|
||||
```bash
|
||||
# Install dependencies
|
||||
pnpm install
|
||||
|
||||
# Start all services (development)
|
||||
pnpm dev
|
||||
|
||||
# Start specific service
|
||||
pnpm --filter @goodgo/auth-service dev
|
||||
|
||||
# Build all
|
||||
pnpm build
|
||||
|
||||
# Run tests
|
||||
pnpm test
|
||||
|
||||
# Run linter
|
||||
pnpm lint
|
||||
```
|
||||
|
||||
### Local Development
|
||||
|
||||
```bash
|
||||
# Setup Neon database (first time only)
|
||||
./scripts/db/setup-neon.sh
|
||||
# Or manually: Create .env.local with Neon DATABASE_URL
|
||||
|
||||
# Start infrastructure (Redis, Traefik - no PostgreSQL needed)
|
||||
cd deployments/local
|
||||
docker-compose up -d
|
||||
|
||||
# Run migrations
|
||||
./scripts/db/migrate.sh auth-service dev
|
||||
|
||||
# Start services
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
**Note**: This project uses Neon PostgreSQL. No local PostgreSQL setup needed!
|
||||
|
||||
## 🛠️ Tech Stack
|
||||
|
||||
**Frontend:**
|
||||
- Web: Next.js 14+ (App Router) - Admin & Client portals
|
||||
- Mobile: Flutter 3.x - Admin & Client apps
|
||||
- TypeScript / Dart
|
||||
- Tailwind CSS
|
||||
- Zustand / Provider
|
||||
|
||||
**Backend:**
|
||||
- Node.js + TypeScript
|
||||
- Express/NestJS
|
||||
- Prisma ORM
|
||||
- PostgreSQL (Neon - serverless)
|
||||
- Redis
|
||||
|
||||
**Infrastructure:**
|
||||
- Traefik (API Gateway)
|
||||
- Docker & Docker Compose
|
||||
- Kubernetes (production)
|
||||
- Prometheus + Grafana + Loki
|
||||
- OpenTelemetry
|
||||
|
||||
**DevOps:**
|
||||
- GitHub Actions
|
||||
- PNPM Workspaces
|
||||
- Turborepo
|
||||
|
||||
## 📚 Documentation
|
||||
|
||||
- [Architecture Overview](docs/architecture/system-design.md)
|
||||
- [API Documentation](docs/api/openapi/)
|
||||
- [Development Guide](docs/guides/development.md)
|
||||
- [Deployment Guide](docs/guides/deployment.md)
|
||||
- [Contributing Guide](CONTRIBUTING.md)
|
||||
|
||||
## 🔐 Environment Variables
|
||||
|
||||
### Database (Neon)
|
||||
|
||||
All environments use Neon PostgreSQL:
|
||||
- **Dev**: Set `DATABASE_URL` in `deployments/local/.env.local`
|
||||
- **Staging/Prod**: Set in Kubernetes secrets or GitHub Secrets
|
||||
|
||||
See `infra/databases/neon/README.md` for setup instructions.
|
||||
|
||||
### Other Variables
|
||||
|
||||
Copy `.env.example` files in each service/app and configure accordingly.
|
||||
|
||||
**Important**: Never commit `.env` files. Use `.env.example` as templates.
|
||||
|
||||
## 🧪 Testing
|
||||
|
||||
```bash
|
||||
# Run all tests
|
||||
pnpm test
|
||||
|
||||
# Run tests for specific package
|
||||
pnpm --filter @goodgo/auth-service test
|
||||
|
||||
# Run with coverage
|
||||
pnpm --filter @goodgo/auth-service test:coverage
|
||||
```
|
||||
|
||||
## 📦 Building
|
||||
|
||||
```bash
|
||||
# Build all packages and services
|
||||
pnpm build
|
||||
|
||||
# Build specific service
|
||||
pnpm --filter @goodgo/auth-service build
|
||||
```
|
||||
|
||||
## 🚢 Deployment
|
||||
|
||||
See [Deployment Guide](docs/guides/deployment.md) for detailed instructions.
|
||||
|
||||
### Quick Deploy
|
||||
|
||||
```bash
|
||||
# Local
|
||||
cd deployments/local && docker-compose up -d
|
||||
|
||||
# Staging (Kubernetes)
|
||||
cd deployments/staging && kubectl apply -f kubernetes/
|
||||
|
||||
# Production
|
||||
cd deployments/production && kubectl apply -f kubernetes/
|
||||
```
|
||||
|
||||
## 🤝 Contributing
|
||||
|
||||
Please read [CONTRIBUTING.md](CONTRIBUTING.md) for details on our code of conduct and the process for submitting pull requests.
|
||||
|
||||
## 📄 License
|
||||
|
||||
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
||||
|
||||
## 👥 Team
|
||||
|
||||
Built with ❤️ by the GoodGo team
|
||||
265
SETUP_GUIDE.md
Normal file
265
SETUP_GUIDE.md
Normal file
@@ -0,0 +1,265 @@
|
||||
# Environment Setup Guide - Hướng Dẫn Thiết Lập Môi Trường
|
||||
|
||||
## 🎯 Tổng Quan
|
||||
|
||||
Dự án sử dụng **Hybrid Environment Configuration** với 2 levels:
|
||||
|
||||
1. **Shared Environment** (`deployments/local/.env.local`) - Configs dùng chung
|
||||
2. **Service-Specific Environment** (`services/<service>/.env.local`) - Configs riêng
|
||||
|
||||
### Lợi Ích
|
||||
|
||||
- ✅ **JWT secrets giống nhau** → Services có thể verify tokens của nhau
|
||||
- ✅ **Mỗi service có database riêng** → Đúng microservices pattern
|
||||
- ✅ **Override configs dễ dàng** → REDIS_HOST khác nhau cho Docker/Native
|
||||
- ✅ **Không duplicate** → Maintain dễ hơn
|
||||
|
||||
## 🚀 Quick Start
|
||||
|
||||
### Option 1: Sử Dụng Script (Khuyến nghị)
|
||||
|
||||
```bash
|
||||
# Tự động tạo tất cả env files
|
||||
./scripts/dev/setup-env.sh
|
||||
```
|
||||
|
||||
### Option 2: Setup Thủ Công
|
||||
|
||||
**Step 1: Shared Environment**
|
||||
```bash
|
||||
cp deployments/local/env.local.example deployments/local/.env.local
|
||||
```
|
||||
|
||||
**Step 2: Service Environment**
|
||||
```bash
|
||||
cp services/auth-service/env.local.example services/auth-service/.env.local
|
||||
```
|
||||
|
||||
## 📝 Cấu Hình Chi Tiết
|
||||
|
||||
### 1. Shared Environment (`deployments/local/.env.local`)
|
||||
|
||||
Chứa configs dùng chung cho TẤT CẢ services:
|
||||
|
||||
```bash
|
||||
# JWT Secrets - PHẢI GIỐNG NHAU cho tất cả services
|
||||
JWT_SECRET=dev-jwt-secret-change-in-production-min-32-chars
|
||||
JWT_REFRESH_SECRET=dev-refresh-secret-change-in-production-min-32-chars
|
||||
|
||||
# Redis (Docker hostname)
|
||||
REDIS_HOST=redis
|
||||
REDIS_PORT=6379
|
||||
|
||||
# Common
|
||||
NODE_ENV=development
|
||||
LOG_LEVEL=debug
|
||||
CORS_ORIGIN=http://localhost:3000,http://localhost:3001
|
||||
```
|
||||
|
||||
### 2. Service Environment (`services/auth-service/.env.local`)
|
||||
|
||||
Chứa configs RIÊNG cho từng service:
|
||||
|
||||
```bash
|
||||
# Database riêng cho service này
|
||||
DATABASE_URL=postgresql://user:pass@ep-xxx.neon.tech/goodgo_auth_dev?sslmode=require&pgbouncer=true
|
||||
|
||||
# Service configs
|
||||
PORT=5001
|
||||
SERVICE_NAME=auth-service
|
||||
|
||||
# Override cho native dev (Redis trong Docker)
|
||||
REDIS_HOST=localhost
|
||||
```
|
||||
|
||||
## 🗄️ Database Setup
|
||||
|
||||
### Tạo Databases Trong Neon
|
||||
|
||||
Mỗi service cần database riêng:
|
||||
|
||||
1. Truy cập: https://console.neon.tech
|
||||
2. Tạo databases:
|
||||
- `goodgo_auth_dev` → auth-service
|
||||
- `goodgo_user_dev` → user-service
|
||||
- `goodgo_product_dev` → product-service
|
||||
- etc.
|
||||
|
||||
3. Copy connection string vào `services/<service>/.env.local`:
|
||||
```bash
|
||||
DATABASE_URL=postgresql://user:pass@ep-xxx.neon.tech/goodgo_auth_dev?sslmode=require&pgbouncer=true
|
||||
```
|
||||
|
||||
### Run Migrations
|
||||
|
||||
```bash
|
||||
# Auth service
|
||||
./scripts/db/migrate.sh auth-service dev
|
||||
|
||||
# Seed data (optional)
|
||||
./scripts/db/seed.sh auth-service
|
||||
```
|
||||
|
||||
## 🔧 Cách Hoạt Động
|
||||
|
||||
### Loading Order
|
||||
|
||||
Services load environment theo thứ tự:
|
||||
|
||||
1. **Shared env** (`deployments/local/.env.local`)
|
||||
- JWT_SECRET, JWT_REFRESH_SECRET
|
||||
- REDIS_HOST=redis, REDIS_PORT
|
||||
- NODE_ENV, LOG_LEVEL, CORS_ORIGIN
|
||||
|
||||
2. **Service env** (`.env.local`)
|
||||
- DATABASE_URL (riêng cho service)
|
||||
- PORT (riêng cho service)
|
||||
- REDIS_HOST=localhost (override cho native dev)
|
||||
|
||||
### Package.json Configuration
|
||||
|
||||
```json
|
||||
{
|
||||
"scripts": {
|
||||
"dev": "dotenv -e ../../deployments/local/.env.local -e .env.local -- tsx watch src/main.ts"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Sử dụng `dotenv-cli` để load multiple env files.
|
||||
|
||||
## 📂 File Structure
|
||||
|
||||
```
|
||||
Base/
|
||||
├── deployments/local/
|
||||
│ ├── env.local.example # Template cho shared env
|
||||
│ └── .env.local # Shared env (gitignored)
|
||||
│
|
||||
├── services/
|
||||
│ ├── auth-service/
|
||||
│ │ ├── env.example # Old template (deprecated)
|
||||
│ │ ├── env.local.example # New template cho service env
|
||||
│ │ └── .env.local # Service env (gitignored)
|
||||
│ │
|
||||
│ └── user-service/
|
||||
│ ├── env.local.example
|
||||
│ └── .env.local
|
||||
│
|
||||
└── scripts/dev/
|
||||
└── setup-env.sh # Auto setup script
|
||||
```
|
||||
|
||||
## ⚠️ Important Notes
|
||||
|
||||
### 1. JWT Secrets
|
||||
|
||||
**MUST BE IDENTICAL** across all services:
|
||||
- Services cần verify JWT tokens của nhau
|
||||
- Nếu khác nhau → authentication sẽ fail
|
||||
|
||||
### 2. Database URLs
|
||||
|
||||
**MUST BE DIFFERENT** for each service:
|
||||
- Mỗi service có database riêng (microservices pattern)
|
||||
- Ví dụ:
|
||||
- auth-service → `goodgo_auth_dev`
|
||||
- user-service → `goodgo_user_dev`
|
||||
|
||||
### 3. Redis Host
|
||||
|
||||
**Khác nhau tùy môi trường:**
|
||||
- **Shared env**: `REDIS_HOST=redis` (Docker hostname)
|
||||
- **Service env**: `REDIS_HOST=localhost` (override cho native dev)
|
||||
|
||||
Khi chạy native, Redis trong Docker → dùng `localhost`
|
||||
|
||||
### 4. Git Ignore
|
||||
|
||||
Tất cả `.env.local` files đã được gitignored:
|
||||
- `deployments/local/.env.local`
|
||||
- `services/*/.env.local`
|
||||
|
||||
**NEVER commit** actual env files!
|
||||
|
||||
## 🎓 Examples
|
||||
|
||||
### Example 1: Auth Service Native Dev
|
||||
|
||||
```bash
|
||||
# 1. Setup env
|
||||
cp services/auth-service/env.local.example services/auth-service/.env.local
|
||||
|
||||
# 2. Edit .env.local
|
||||
DATABASE_URL=postgresql://user:pass@ep-xxx.neon.tech/goodgo_auth_dev?sslmode=require
|
||||
PORT=5001
|
||||
REDIS_HOST=localhost # Override cho native dev
|
||||
|
||||
# 3. Start Redis in Docker
|
||||
cd deployments/local && docker-compose up -d redis
|
||||
|
||||
# 4. Run service
|
||||
pnpm --filter @goodgo/auth-service dev
|
||||
```
|
||||
|
||||
### Example 2: Multiple Services
|
||||
|
||||
```bash
|
||||
# 1. Setup all envs
|
||||
./scripts/dev/setup-env.sh
|
||||
|
||||
# 2. Edit each service's .env.local with different DATABASE_URL
|
||||
|
||||
# 3. Start infrastructure
|
||||
cd deployments/local && docker-compose up -d redis traefik
|
||||
|
||||
# 4. Run all services
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
## 🔍 Troubleshooting
|
||||
|
||||
### Database Connection Failed
|
||||
|
||||
```bash
|
||||
# Check if .env.local exists
|
||||
ls -la services/auth-service/.env.local
|
||||
|
||||
# Check DATABASE_URL
|
||||
cat services/auth-service/.env.local | grep DATABASE_URL
|
||||
|
||||
# Test connection
|
||||
cd services/auth-service && pnpm prisma db pull
|
||||
```
|
||||
|
||||
### JWT Secret Warning
|
||||
|
||||
Nếu thấy warning "Using default JWT_SECRET":
|
||||
- Check `deployments/local/.env.local` có JWT_SECRET chưa
|
||||
- Check service có load đúng env files chưa
|
||||
|
||||
### Redis Connection Failed
|
||||
|
||||
Khi chạy native:
|
||||
- Check Redis container đang chạy: `docker ps | grep redis`
|
||||
- Check REDIS_HOST=localhost trong service `.env.local`
|
||||
|
||||
## 📚 Tài Liệu Thêm
|
||||
|
||||
- [Local Development Guide](docs/vi/guides/local-development.md)
|
||||
- [Neon Database Guide](docs/vi/guides/neon-database.md)
|
||||
- [Service README](services/auth-service/README.md)
|
||||
|
||||
## 🎉 Ready to Go!
|
||||
|
||||
Sau khi setup xong:
|
||||
|
||||
```bash
|
||||
# Start everything
|
||||
./scripts/dev/start-all.sh
|
||||
|
||||
# Or selective
|
||||
pnpm --filter @goodgo/auth-service dev
|
||||
```
|
||||
|
||||
Happy coding! 🚀
|
||||
44
apps/app-admin/.gitignore
vendored
Normal file
44
apps/app-admin/.gitignore
vendored
Normal file
@@ -0,0 +1,44 @@
|
||||
# Miscellaneous
|
||||
*.class
|
||||
*.log
|
||||
*.pyc
|
||||
*.swp
|
||||
.DS_Store
|
||||
.atom/
|
||||
.buildlog/
|
||||
.history
|
||||
.svn/
|
||||
migrate_working_dir/
|
||||
|
||||
# IntelliJ related
|
||||
*.iml
|
||||
*.ipr
|
||||
*.iws
|
||||
.idea/
|
||||
|
||||
# The .vscode folder contains launch configuration and tasks you configure in
|
||||
# VS Code which you may wish to be included in version control, so this line
|
||||
# is commented out by default.
|
||||
#.vscode/
|
||||
|
||||
# Flutter/Dart/Pub related
|
||||
**/doc/api/
|
||||
**/ios/Flutter/.last_build_id
|
||||
.dart_tool/
|
||||
.flutter-plugins
|
||||
.flutter-plugins-dependencies
|
||||
.packages
|
||||
.pub-cache/
|
||||
.pub/
|
||||
/build/
|
||||
|
||||
# Symbolication related
|
||||
app.*.symbols
|
||||
|
||||
# Obfuscation related
|
||||
app.*.map.json
|
||||
|
||||
# Android Studio will place build artifacts here
|
||||
/android/app/debug
|
||||
/android/app/profile
|
||||
/android/app/release
|
||||
20
apps/app-admin/Dockerfile
Normal file
20
apps/app-admin/Dockerfile
Normal file
@@ -0,0 +1,20 @@
|
||||
# Flutter build stage
|
||||
FROM ghcr.io/cirruslabs/flutter:stable AS build
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Copy pubspec files
|
||||
COPY pubspec.yaml pubspec.lock ./
|
||||
|
||||
# Get dependencies
|
||||
RUN flutter pub get
|
||||
|
||||
# Copy source code
|
||||
COPY . .
|
||||
|
||||
# Build APK (for CI/CD purposes)
|
||||
# Note: Actual mobile builds require native tooling
|
||||
RUN flutter build apk --release || echo "Build skipped - requires Android SDK"
|
||||
|
||||
# For CI/CD testing only
|
||||
CMD ["flutter", "test"]
|
||||
43
apps/app-admin/README.md
Normal file
43
apps/app-admin/README.md
Normal file
@@ -0,0 +1,43 @@
|
||||
# App Admin (Flutter)
|
||||
|
||||
Flutter mobile application for GoodGo Platform Admin Panel.
|
||||
|
||||
## Features
|
||||
|
||||
- Flutter 3.x
|
||||
- Material Design 3
|
||||
- GoRouter for navigation
|
||||
- Provider for state management
|
||||
- API integration
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Flutter SDK >= 3.0.0
|
||||
- Dart SDK >= 3.0.0
|
||||
- Android Studio / Xcode
|
||||
|
||||
## Development
|
||||
|
||||
```bash
|
||||
# Install dependencies
|
||||
flutter pub get
|
||||
|
||||
# Run on Android
|
||||
flutter run
|
||||
|
||||
# Run on iOS
|
||||
flutter run -d ios
|
||||
|
||||
# Build APK
|
||||
flutter build apk
|
||||
|
||||
# Build iOS
|
||||
flutter build ios
|
||||
```
|
||||
|
||||
## Environment Variables
|
||||
|
||||
Create `.env` file:
|
||||
```
|
||||
API_URL=http://localhost/api/v1
|
||||
```
|
||||
71
apps/app-admin/lib/main.dart
Normal file
71
apps/app-admin/lib/main.dart
Normal file
@@ -0,0 +1,71 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
void main() {
|
||||
runApp(const MyApp());
|
||||
}
|
||||
|
||||
class MyApp extends StatelessWidget {
|
||||
const MyApp({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp.router(
|
||||
title: 'GoodGo Admin',
|
||||
theme: ThemeData(
|
||||
colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue),
|
||||
useMaterial3: true,
|
||||
),
|
||||
routerConfig: _router,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
final GoRouter _router = GoRouter(
|
||||
routes: <RouteBase>[
|
||||
GoRoute(
|
||||
path: '/',
|
||||
builder: (BuildContext context, GoRouterState state) {
|
||||
return const HomeScreen();
|
||||
},
|
||||
),
|
||||
GoRoute(
|
||||
path: '/login',
|
||||
builder: (BuildContext context, GoRouterState state) {
|
||||
return const LoginScreen();
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
class HomeScreen extends StatelessWidget {
|
||||
const HomeScreen({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('GoodGo Admin'),
|
||||
),
|
||||
body: const Center(
|
||||
child: Text('Welcome to GoodGo Admin'),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class LoginScreen extends StatelessWidget {
|
||||
const LoginScreen({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Login'),
|
||||
),
|
||||
body: const Center(
|
||||
child: Text('Login Screen'),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
24
apps/app-admin/pubspec.yaml
Normal file
24
apps/app-admin/pubspec.yaml
Normal file
@@ -0,0 +1,24 @@
|
||||
name: app_admin
|
||||
description: GoodGo Platform Admin Mobile Application (Flutter)
|
||||
publish_to: 'none'
|
||||
version: 1.0.0+1
|
||||
|
||||
environment:
|
||||
sdk: '>=3.0.0 <4.0.0'
|
||||
|
||||
dependencies:
|
||||
flutter:
|
||||
sdk: flutter
|
||||
cupertino_icons: ^1.0.6
|
||||
http: ^1.1.0
|
||||
shared_preferences: ^2.2.2
|
||||
provider: ^6.1.1
|
||||
go_router: ^13.0.0
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
flutter_lints: ^3.0.0
|
||||
|
||||
flutter:
|
||||
uses-material-design: true
|
||||
20
apps/app-client/Dockerfile
Normal file
20
apps/app-client/Dockerfile
Normal file
@@ -0,0 +1,20 @@
|
||||
# Flutter build stage
|
||||
FROM ghcr.io/cirruslabs/flutter:stable AS build
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Copy pubspec files
|
||||
COPY pubspec.yaml pubspec.lock ./
|
||||
|
||||
# Get dependencies
|
||||
RUN flutter pub get
|
||||
|
||||
# Copy source code
|
||||
COPY . .
|
||||
|
||||
# Build APK (for CI/CD purposes)
|
||||
# Note: Actual mobile builds require native tooling
|
||||
RUN flutter build apk --release || echo "Build skipped - requires Android SDK"
|
||||
|
||||
# For CI/CD testing only
|
||||
CMD ["flutter", "test"]
|
||||
43
apps/app-client/README.md
Normal file
43
apps/app-client/README.md
Normal file
@@ -0,0 +1,43 @@
|
||||
# App Client (Flutter)
|
||||
|
||||
Flutter mobile application for GoodGo Platform Client Portal.
|
||||
|
||||
## Features
|
||||
|
||||
- Flutter 3.x
|
||||
- Material Design 3
|
||||
- GoRouter for navigation
|
||||
- Provider for state management
|
||||
- API integration
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Flutter SDK >= 3.0.0
|
||||
- Dart SDK >= 3.0.0
|
||||
- Android Studio / Xcode
|
||||
|
||||
## Development
|
||||
|
||||
```bash
|
||||
# Install dependencies
|
||||
flutter pub get
|
||||
|
||||
# Run on Android
|
||||
flutter run
|
||||
|
||||
# Run on iOS
|
||||
flutter run -d ios
|
||||
|
||||
# Build APK
|
||||
flutter build apk
|
||||
|
||||
# Build iOS
|
||||
flutter build ios
|
||||
```
|
||||
|
||||
## Environment Variables
|
||||
|
||||
Create `.env` file:
|
||||
```
|
||||
API_URL=http://localhost/api/v1
|
||||
```
|
||||
71
apps/app-client/lib/main.dart
Normal file
71
apps/app-client/lib/main.dart
Normal file
@@ -0,0 +1,71 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
void main() {
|
||||
runApp(const MyApp());
|
||||
}
|
||||
|
||||
class MyApp extends StatelessWidget {
|
||||
const MyApp({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp.router(
|
||||
title: 'GoodGo Client',
|
||||
theme: ThemeData(
|
||||
colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue),
|
||||
useMaterial3: true,
|
||||
),
|
||||
routerConfig: _router,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
final GoRouter _router = GoRouter(
|
||||
routes: <RouteBase>[
|
||||
GoRoute(
|
||||
path: '/',
|
||||
builder: (BuildContext context, GoRouterState state) {
|
||||
return const HomeScreen();
|
||||
},
|
||||
),
|
||||
GoRoute(
|
||||
path: '/login',
|
||||
builder: (BuildContext context, GoRouterState state) {
|
||||
return const LoginScreen();
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
class HomeScreen extends StatelessWidget {
|
||||
const HomeScreen({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('GoodGo Client'),
|
||||
),
|
||||
body: const Center(
|
||||
child: Text('Welcome to GoodGo Client'),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class LoginScreen extends StatelessWidget {
|
||||
const LoginScreen({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Login'),
|
||||
),
|
||||
body: const Center(
|
||||
child: Text('Login Screen'),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
24
apps/app-client/pubspec.yaml
Normal file
24
apps/app-client/pubspec.yaml
Normal file
@@ -0,0 +1,24 @@
|
||||
name: app_client
|
||||
description: GoodGo Platform Client Mobile Application (Flutter)
|
||||
publish_to: 'none'
|
||||
version: 1.0.0+1
|
||||
|
||||
environment:
|
||||
sdk: '>=3.0.0 <4.0.0'
|
||||
|
||||
dependencies:
|
||||
flutter:
|
||||
sdk: flutter
|
||||
cupertino_icons: ^1.0.6
|
||||
http: ^1.1.0
|
||||
shared_preferences: ^2.2.2
|
||||
provider: ^6.1.1
|
||||
go_router: ^13.0.0
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
flutter_lints: ^3.0.0
|
||||
|
||||
flutter:
|
||||
uses-material-design: true
|
||||
50
apps/web-admin/Dockerfile
Normal file
50
apps/web-admin/Dockerfile
Normal file
@@ -0,0 +1,50 @@
|
||||
FROM node:20-alpine AS base
|
||||
RUN apk add --no-cache libc6-compat
|
||||
WORKDIR /app
|
||||
|
||||
# Builder stage
|
||||
FROM base AS builder
|
||||
RUN corepack enable pnpm
|
||||
# Copy workspace configuration
|
||||
COPY pnpm-workspace.yaml package.json pnpm-lock.yaml ./
|
||||
# Create directory structure and copy all package.json files
|
||||
RUN mkdir -p packages apps services
|
||||
COPY packages/auth-sdk/package.json ./packages/auth-sdk/
|
||||
COPY packages/http-client/package.json ./packages/http-client/
|
||||
COPY packages/logger/package.json ./packages/logger/
|
||||
COPY packages/tracing/package.json ./packages/tracing/
|
||||
COPY packages/types/package.json ./packages/types/
|
||||
COPY packages/config/eslint-config/package.json ./packages/config/eslint-config/
|
||||
COPY packages/config/prettier-config/package.json ./packages/config/prettier-config/
|
||||
COPY packages/config/tsconfig/package.json ./packages/config/tsconfig/
|
||||
COPY apps/web-client/package.json ./apps/web-client/
|
||||
COPY apps/web-admin/package.json ./apps/web-admin/
|
||||
COPY services/auth-service/package.json ./services/auth-service/
|
||||
# Install all dependencies for entire monorepo
|
||||
RUN --mount=type=cache,id=pnpm,target=/root/.local/share/pnpm/store \
|
||||
pnpm install --frozen-lockfile
|
||||
# Copy all source code
|
||||
COPY packages ./packages
|
||||
COPY apps/web-admin ./apps/web-admin
|
||||
COPY turbo.json ./
|
||||
# Build using turbo from root (handles dependency order automatically)
|
||||
RUN pnpm turbo build --filter=web-admin
|
||||
|
||||
# Production stage
|
||||
FROM base AS runner
|
||||
ENV NODE_ENV=production
|
||||
RUN addgroup --system --gid 1001 nodejs
|
||||
RUN adduser --system --uid 1001 nextjs
|
||||
|
||||
# Copy the entire workspace to preserve pnpm structure
|
||||
COPY --from=builder --chown=nextjs:nodejs /app /app
|
||||
|
||||
WORKDIR /app/apps/web-admin
|
||||
|
||||
USER nextjs
|
||||
|
||||
EXPOSE 3000
|
||||
ENV PORT=3000
|
||||
ENV HOSTNAME="0.0.0.0"
|
||||
|
||||
CMD ["node", ".next/standalone/apps/web-admin/server.js"]
|
||||
31
apps/web-admin/README.md
Normal file
31
apps/web-admin/README.md
Normal file
@@ -0,0 +1,31 @@
|
||||
# Web Admin Application
|
||||
|
||||
Next.js web application for GoodGo Platform Admin Panel.
|
||||
|
||||
## Features
|
||||
|
||||
- Next.js 14 with App Router
|
||||
- TypeScript
|
||||
- Tailwind CSS
|
||||
- Zustand for state management
|
||||
- API integration with auth service
|
||||
|
||||
## Development
|
||||
|
||||
```bash
|
||||
# Install dependencies
|
||||
pnpm install
|
||||
|
||||
# Start development server
|
||||
pnpm dev
|
||||
|
||||
# Build for production
|
||||
pnpm build
|
||||
|
||||
# Start production server
|
||||
pnpm start
|
||||
```
|
||||
|
||||
## Environment Variables
|
||||
|
||||
- `NEXT_PUBLIC_API_URL` - API base URL (default: http://localhost/api/v1)
|
||||
5
apps/web-admin/next-env.d.ts
vendored
Normal file
5
apps/web-admin/next-env.d.ts
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
/// <reference types="next" />
|
||||
/// <reference types="next/image-types/global" />
|
||||
|
||||
// NOTE: This file should not be edited
|
||||
// see https://nextjs.org/docs/app/building-your-application/configuring/typescript for more information.
|
||||
10
apps/web-admin/next.config.js
Normal file
10
apps/web-admin/next.config.js
Normal file
@@ -0,0 +1,10 @@
|
||||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {
|
||||
reactStrictMode: true,
|
||||
output: 'standalone',
|
||||
env: {
|
||||
NEXT_PUBLIC_API_URL: process.env.NEXT_PUBLIC_API_URL || 'http://localhost/api/v1',
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = nextConfig;
|
||||
35
apps/web-admin/package.json
Normal file
35
apps/web-admin/package.json
Normal file
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"name": "@goodgo/web-admin",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"lint": "next lint",
|
||||
"typecheck": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@goodgo/types": "workspace:*",
|
||||
"@goodgo/http-client": "workspace:*",
|
||||
"next": "^14.1.0",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"zustand": "^4.4.7",
|
||||
"axios": "^1.6.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@goodgo/eslint-config": "workspace:*",
|
||||
"@goodgo/tsconfig": "workspace:*",
|
||||
"@goodgo/prettier-config": "workspace:*",
|
||||
"@types/node": "^20.11.0",
|
||||
"@types/react": "^18.2.48",
|
||||
"@types/react-dom": "^18.2.18",
|
||||
"typescript": "^5.3.3",
|
||||
"tailwindcss": "^3.4.1",
|
||||
"postcss": "^8.4.33",
|
||||
"autoprefixer": "^10.4.17",
|
||||
"eslint": "^8.56.0",
|
||||
"eslint-config-next": "^14.1.0"
|
||||
}
|
||||
}
|
||||
6
apps/web-admin/postcss.config.js
Normal file
6
apps/web-admin/postcss.config.js
Normal file
@@ -0,0 +1,6 @@
|
||||
module.exports = {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
};
|
||||
1
apps/web-admin/public/.gitkeep
Normal file
1
apps/web-admin/public/.gitkeep
Normal file
@@ -0,0 +1 @@
|
||||
# Public assets
|
||||
27
apps/web-admin/src/app/globals.css
Normal file
27
apps/web-admin/src/app/globals.css
Normal file
@@ -0,0 +1,27 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
:root {
|
||||
--foreground-rgb: 0, 0, 0;
|
||||
--background-start-rgb: 214, 219, 220;
|
||||
--background-end-rgb: 255, 255, 255;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--foreground-rgb: 255, 255, 255;
|
||||
--background-start-rgb: 0, 0, 0;
|
||||
--background-end-rgb: 0, 0, 0;
|
||||
}
|
||||
}
|
||||
|
||||
body {
|
||||
color: rgb(var(--foreground-rgb));
|
||||
background: linear-gradient(
|
||||
to bottom,
|
||||
transparent,
|
||||
rgb(var(--background-end-rgb))
|
||||
)
|
||||
rgb(var(--background-start-rgb));
|
||||
}
|
||||
19
apps/web-admin/src/app/layout.tsx
Normal file
19
apps/web-admin/src/app/layout.tsx
Normal file
@@ -0,0 +1,19 @@
|
||||
import type { Metadata } from 'next';
|
||||
import './globals.css';
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'GoodGo Platform',
|
||||
description: 'Enterprise microservices platform',
|
||||
};
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body>{children}</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
60
apps/web-admin/src/app/login/page.tsx
Normal file
60
apps/web-admin/src/app/login/page.tsx
Normal file
@@ -0,0 +1,60 @@
|
||||
'use client';
|
||||
|
||||
import { useState } from 'react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useAuthStore } from '@/stores/auth.store';
|
||||
|
||||
export default function LoginPage() {
|
||||
const router = useRouter();
|
||||
const { login, isLoading } = useAuthStore();
|
||||
const [email, setEmail] = useState('');
|
||||
const [password, setPassword] = useState('');
|
||||
const [error, setError] = useState('');
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
setError('');
|
||||
try {
|
||||
await login(email, password);
|
||||
router.push('/');
|
||||
} catch (err: any) {
|
||||
setError(err.message || 'Login failed');
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center">
|
||||
<form onSubmit={handleSubmit} className="bg-white p-8 rounded shadow-md w-96">
|
||||
<h2 className="text-2xl font-bold mb-4">Login</h2>
|
||||
{error && <div className="text-red-500 mb-4">{error}</div>}
|
||||
<div className="mb-4">
|
||||
<label className="block mb-2">Email</label>
|
||||
<input
|
||||
type="email"
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
className="w-full p-2 border rounded"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-4">
|
||||
<label className="block mb-2">Password</label>
|
||||
<input
|
||||
type="password"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
className="w-full p-2 border rounded"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
type="submit"
|
||||
disabled={isLoading}
|
||||
className="w-full bg-blue-500 text-white p-2 rounded hover:bg-blue-600 disabled:opacity-50"
|
||||
>
|
||||
{isLoading ? 'Logging in...' : 'Login'}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
34
apps/web-admin/src/app/page.tsx
Normal file
34
apps/web-admin/src/app/page.tsx
Normal file
@@ -0,0 +1,34 @@
|
||||
'use client';
|
||||
|
||||
import { useAuthStore } from '@/stores/auth.store';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
export default function Home() {
|
||||
const { user, isAuthenticated, isLoading, fetchUser } = useAuthStore();
|
||||
|
||||
useEffect(() => {
|
||||
if (!isAuthenticated && !isLoading) {
|
||||
fetchUser();
|
||||
}
|
||||
}, [isAuthenticated, isLoading, fetchUser]);
|
||||
|
||||
if (isLoading) {
|
||||
return <div className="p-8">Loading...</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<main className="min-h-screen p-8">
|
||||
<h1 className="text-4xl font-bold mb-4">GoodGo Platform</h1>
|
||||
{isAuthenticated && user ? (
|
||||
<div>
|
||||
<p>Welcome, {user.email}!</p>
|
||||
<p>Role: {user.role}</p>
|
||||
</div>
|
||||
) : (
|
||||
<div>
|
||||
<p>Please log in to continue.</p>
|
||||
</div>
|
||||
)}
|
||||
</main>
|
||||
);
|
||||
}
|
||||
45
apps/web-admin/src/services/api/auth.api.ts
Normal file
45
apps/web-admin/src/services/api/auth.api.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import { apiClient } from './client';
|
||||
import { LoginDto, RegisterDto, AuthResponse, ApiResponse, UserResponse } from '@goodgo/types';
|
||||
|
||||
export const authApi = {
|
||||
register: async (data: RegisterDto): Promise<ApiResponse<AuthResponse>> => {
|
||||
return apiClient.post('/auth/register', data);
|
||||
},
|
||||
|
||||
login: async (data: LoginDto): Promise<ApiResponse<AuthResponse>> => {
|
||||
const response = await apiClient.post('/auth/login', data);
|
||||
if (response.success && response.data) {
|
||||
apiClient.setAuthToken(response.data.accessToken);
|
||||
if (typeof window !== 'undefined') {
|
||||
localStorage.setItem('refreshToken', response.data.refreshToken);
|
||||
}
|
||||
}
|
||||
return response;
|
||||
},
|
||||
|
||||
logout: async (): Promise<ApiResponse> => {
|
||||
const refreshToken = typeof window !== 'undefined' ? localStorage.getItem('refreshToken') : null;
|
||||
const response = await apiClient.post('/auth/logout', { refreshToken });
|
||||
apiClient.removeAuthToken();
|
||||
if (typeof window !== 'undefined') {
|
||||
localStorage.removeItem('refreshToken');
|
||||
}
|
||||
return response;
|
||||
},
|
||||
|
||||
refreshToken: async (refreshToken: string): Promise<ApiResponse<{ accessToken: string }>> => {
|
||||
const response = await apiClient.post('/auth/refresh', { refreshToken });
|
||||
if (response.success && response.data) {
|
||||
apiClient.setAuthToken(response.data.accessToken);
|
||||
}
|
||||
return response;
|
||||
},
|
||||
|
||||
getMe: async (): Promise<ApiResponse<UserResponse>> => {
|
||||
return apiClient.get('/users/me');
|
||||
},
|
||||
|
||||
changePassword: async (currentPassword: string, newPassword: string): Promise<ApiResponse> => {
|
||||
return apiClient.put('/auth/password', { currentPassword, newPassword });
|
||||
},
|
||||
};
|
||||
8
apps/web-admin/src/services/api/client.ts
Normal file
8
apps/web-admin/src/services/api/client.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import { createHttpClient } from '@goodgo/http-client';
|
||||
|
||||
const API_URL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost/api/v1';
|
||||
|
||||
export const apiClient = createHttpClient({
|
||||
baseURL: API_URL,
|
||||
timeout: 30000,
|
||||
});
|
||||
103
apps/web-admin/src/stores/auth.store.ts
Normal file
103
apps/web-admin/src/stores/auth.store.ts
Normal file
@@ -0,0 +1,103 @@
|
||||
import { create } from 'zustand';
|
||||
import { persist } from 'zustand/middleware';
|
||||
import { UserResponse } from '@goodgo/types';
|
||||
import { authApi } from '../services/api/auth.api';
|
||||
|
||||
interface AuthState {
|
||||
user: UserResponse | null;
|
||||
isAuthenticated: boolean;
|
||||
isLoading: boolean;
|
||||
login: (email: string, password: string) => Promise<void>;
|
||||
register: (email: string, password: string, confirmPassword: string) => Promise<void>;
|
||||
logout: () => Promise<void>;
|
||||
fetchUser: () => Promise<void>;
|
||||
}
|
||||
|
||||
export const useAuthStore = create<AuthState>()(
|
||||
persist(
|
||||
(set) => ({
|
||||
user: null,
|
||||
isAuthenticated: false,
|
||||
isLoading: false,
|
||||
|
||||
login: async (email: string, password: string) => {
|
||||
set({ isLoading: true });
|
||||
try {
|
||||
const response = await authApi.login({ email, password });
|
||||
if (response.success && response.data) {
|
||||
set({
|
||||
user: response.data.user,
|
||||
isAuthenticated: true,
|
||||
isLoading: false,
|
||||
});
|
||||
} else {
|
||||
throw new Error(response.error?.message || 'Login failed');
|
||||
}
|
||||
} catch (error) {
|
||||
set({ isLoading: false });
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
register: async (email: string, password: string, confirmPassword: string) => {
|
||||
set({ isLoading: true });
|
||||
try {
|
||||
const response = await authApi.register({ email, password, confirmPassword });
|
||||
if (response.success && response.data) {
|
||||
set({
|
||||
user: response.data.user,
|
||||
isAuthenticated: true,
|
||||
isLoading: false,
|
||||
});
|
||||
} else {
|
||||
throw new Error(response.error?.message || 'Registration failed');
|
||||
}
|
||||
} catch (error) {
|
||||
set({ isLoading: false });
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
logout: async () => {
|
||||
try {
|
||||
await authApi.logout();
|
||||
} finally {
|
||||
set({
|
||||
user: null,
|
||||
isAuthenticated: false,
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
fetchUser: async () => {
|
||||
set({ isLoading: true });
|
||||
try {
|
||||
const response = await authApi.getMe();
|
||||
if (response.success && response.data) {
|
||||
set({
|
||||
user: response.data,
|
||||
isAuthenticated: true,
|
||||
isLoading: false,
|
||||
});
|
||||
} else {
|
||||
set({
|
||||
user: null,
|
||||
isAuthenticated: false,
|
||||
isLoading: false,
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
set({
|
||||
user: null,
|
||||
isAuthenticated: false,
|
||||
isLoading: false,
|
||||
});
|
||||
}
|
||||
},
|
||||
}),
|
||||
{
|
||||
name: 'auth-storage',
|
||||
partialize: (state) => ({ user: state.user, isAuthenticated: state.isAuthenticated }),
|
||||
}
|
||||
)
|
||||
);
|
||||
12
apps/web-admin/tailwind.config.js
Normal file
12
apps/web-admin/tailwind.config.js
Normal file
@@ -0,0 +1,12 @@
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
module.exports = {
|
||||
content: [
|
||||
'./src/pages/**/*.{js,ts,jsx,tsx,mdx}',
|
||||
'./src/components/**/*.{js,ts,jsx,tsx,mdx}',
|
||||
'./src/app/**/*.{js,ts,jsx,tsx,mdx}',
|
||||
],
|
||||
theme: {
|
||||
extend: {},
|
||||
},
|
||||
plugins: [],
|
||||
};
|
||||
21
apps/web-admin/tsconfig.json
Normal file
21
apps/web-admin/tsconfig.json
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"extends": "@goodgo/tsconfig/nextjs.json",
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": [
|
||||
"./src/*"
|
||||
]
|
||||
},
|
||||
"isolatedModules": true
|
||||
},
|
||||
"include": [
|
||||
"next-env.d.ts",
|
||||
"**/*.ts",
|
||||
"**/*.tsx",
|
||||
".next/types/**/*.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules"
|
||||
]
|
||||
}
|
||||
50
apps/web-client/Dockerfile
Normal file
50
apps/web-client/Dockerfile
Normal file
@@ -0,0 +1,50 @@
|
||||
FROM node:20-alpine AS base
|
||||
RUN apk add --no-cache libc6-compat
|
||||
WORKDIR /app
|
||||
|
||||
# Builder stage
|
||||
FROM base AS builder
|
||||
RUN corepack enable pnpm
|
||||
# Copy workspace configuration
|
||||
COPY pnpm-workspace.yaml package.json pnpm-lock.yaml ./
|
||||
# Create directory structure and copy all package.json files
|
||||
RUN mkdir -p packages apps services
|
||||
COPY packages/auth-sdk/package.json ./packages/auth-sdk/
|
||||
COPY packages/http-client/package.json ./packages/http-client/
|
||||
COPY packages/logger/package.json ./packages/logger/
|
||||
COPY packages/tracing/package.json ./packages/tracing/
|
||||
COPY packages/types/package.json ./packages/types/
|
||||
COPY packages/config/eslint-config/package.json ./packages/config/eslint-config/
|
||||
COPY packages/config/prettier-config/package.json ./packages/config/prettier-config/
|
||||
COPY packages/config/tsconfig/package.json ./packages/config/tsconfig/
|
||||
COPY apps/web-client/package.json ./apps/web-client/
|
||||
COPY apps/web-admin/package.json ./apps/web-admin/
|
||||
COPY services/auth-service/package.json ./services/auth-service/
|
||||
# Install all dependencies for entire monorepo
|
||||
RUN --mount=type=cache,id=pnpm,target=/root/.local/share/pnpm/store \
|
||||
pnpm install --frozen-lockfile
|
||||
# Copy all source code
|
||||
COPY packages ./packages
|
||||
COPY apps/web-client ./apps/web-client
|
||||
COPY turbo.json ./
|
||||
# Build using turbo from root (handles dependency order automatically)
|
||||
RUN pnpm turbo build --filter=web-client
|
||||
|
||||
# Production stage
|
||||
FROM base AS runner
|
||||
ENV NODE_ENV=production
|
||||
RUN addgroup --system --gid 1001 nodejs
|
||||
RUN adduser --system --uid 1001 nextjs
|
||||
|
||||
# Copy the entire workspace to preserve pnpm structure
|
||||
COPY --from=builder --chown=nextjs:nodejs /app /app
|
||||
|
||||
WORKDIR /app/apps/web-client
|
||||
|
||||
USER nextjs
|
||||
|
||||
EXPOSE 3000
|
||||
ENV PORT=3000
|
||||
ENV HOSTNAME="0.0.0.0"
|
||||
|
||||
CMD ["node", ".next/standalone/apps/web-client/server.js"]
|
||||
31
apps/web-client/README.md
Normal file
31
apps/web-client/README.md
Normal file
@@ -0,0 +1,31 @@
|
||||
# Web Client Application
|
||||
|
||||
Next.js web application for GoodGo Platform Client Portal.
|
||||
|
||||
## Features
|
||||
|
||||
- Next.js 14 with App Router
|
||||
- TypeScript
|
||||
- Tailwind CSS
|
||||
- Zustand for state management
|
||||
- API integration with auth service
|
||||
|
||||
## Development
|
||||
|
||||
```bash
|
||||
# Install dependencies
|
||||
pnpm install
|
||||
|
||||
# Start development server
|
||||
pnpm dev
|
||||
|
||||
# Build for production
|
||||
pnpm build
|
||||
|
||||
# Start production server
|
||||
pnpm start
|
||||
```
|
||||
|
||||
## Environment Variables
|
||||
|
||||
- `NEXT_PUBLIC_API_URL` - API base URL (default: http://localhost/api/v1)
|
||||
5
apps/web-client/next-env.d.ts
vendored
Normal file
5
apps/web-client/next-env.d.ts
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
/// <reference types="next" />
|
||||
/// <reference types="next/image-types/global" />
|
||||
|
||||
// NOTE: This file should not be edited
|
||||
// see https://nextjs.org/docs/app/building-your-application/configuring/typescript for more information.
|
||||
10
apps/web-client/next.config.js
Normal file
10
apps/web-client/next.config.js
Normal file
@@ -0,0 +1,10 @@
|
||||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {
|
||||
reactStrictMode: true,
|
||||
output: 'standalone',
|
||||
env: {
|
||||
NEXT_PUBLIC_API_URL: process.env.NEXT_PUBLIC_API_URL || 'http://localhost/api/v1',
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = nextConfig;
|
||||
35
apps/web-client/package.json
Normal file
35
apps/web-client/package.json
Normal file
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"name": "@goodgo/web-client",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"lint": "next lint",
|
||||
"typecheck": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@goodgo/types": "workspace:*",
|
||||
"@goodgo/http-client": "workspace:*",
|
||||
"next": "^14.1.0",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"zustand": "^4.4.7",
|
||||
"axios": "^1.6.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@goodgo/eslint-config": "workspace:*",
|
||||
"@goodgo/tsconfig": "workspace:*",
|
||||
"@goodgo/prettier-config": "workspace:*",
|
||||
"@types/node": "^20.11.0",
|
||||
"@types/react": "^18.2.48",
|
||||
"@types/react-dom": "^18.2.18",
|
||||
"typescript": "^5.3.3",
|
||||
"tailwindcss": "^3.4.1",
|
||||
"postcss": "^8.4.33",
|
||||
"autoprefixer": "^10.4.17",
|
||||
"eslint": "^8.56.0",
|
||||
"eslint-config-next": "^14.1.0"
|
||||
}
|
||||
}
|
||||
6
apps/web-client/postcss.config.js
Normal file
6
apps/web-client/postcss.config.js
Normal file
@@ -0,0 +1,6 @@
|
||||
module.exports = {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
};
|
||||
1
apps/web-client/public/.gitkeep
Normal file
1
apps/web-client/public/.gitkeep
Normal file
@@ -0,0 +1 @@
|
||||
# Public assets
|
||||
27
apps/web-client/src/app/globals.css
Normal file
27
apps/web-client/src/app/globals.css
Normal file
@@ -0,0 +1,27 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
:root {
|
||||
--foreground-rgb: 0, 0, 0;
|
||||
--background-start-rgb: 214, 219, 220;
|
||||
--background-end-rgb: 255, 255, 255;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--foreground-rgb: 255, 255, 255;
|
||||
--background-start-rgb: 0, 0, 0;
|
||||
--background-end-rgb: 0, 0, 0;
|
||||
}
|
||||
}
|
||||
|
||||
body {
|
||||
color: rgb(var(--foreground-rgb));
|
||||
background: linear-gradient(
|
||||
to bottom,
|
||||
transparent,
|
||||
rgb(var(--background-end-rgb))
|
||||
)
|
||||
rgb(var(--background-start-rgb));
|
||||
}
|
||||
19
apps/web-client/src/app/layout.tsx
Normal file
19
apps/web-client/src/app/layout.tsx
Normal file
@@ -0,0 +1,19 @@
|
||||
import type { Metadata } from 'next';
|
||||
import './globals.css';
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'GoodGo Platform',
|
||||
description: 'Enterprise microservices platform',
|
||||
};
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body>{children}</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
60
apps/web-client/src/app/login/page.tsx
Normal file
60
apps/web-client/src/app/login/page.tsx
Normal file
@@ -0,0 +1,60 @@
|
||||
'use client';
|
||||
|
||||
import { useState } from 'react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useAuthStore } from '@/stores/auth.store';
|
||||
|
||||
export default function LoginPage() {
|
||||
const router = useRouter();
|
||||
const { login, isLoading } = useAuthStore();
|
||||
const [email, setEmail] = useState('');
|
||||
const [password, setPassword] = useState('');
|
||||
const [error, setError] = useState('');
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
setError('');
|
||||
try {
|
||||
await login(email, password);
|
||||
router.push('/');
|
||||
} catch (err: any) {
|
||||
setError(err.message || 'Login failed');
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center">
|
||||
<form onSubmit={handleSubmit} className="bg-white p-8 rounded shadow-md w-96">
|
||||
<h2 className="text-2xl font-bold mb-4">Login</h2>
|
||||
{error && <div className="text-red-500 mb-4">{error}</div>}
|
||||
<div className="mb-4">
|
||||
<label className="block mb-2">Email</label>
|
||||
<input
|
||||
type="email"
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
className="w-full p-2 border rounded"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-4">
|
||||
<label className="block mb-2">Password</label>
|
||||
<input
|
||||
type="password"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
className="w-full p-2 border rounded"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
type="submit"
|
||||
disabled={isLoading}
|
||||
className="w-full bg-blue-500 text-white p-2 rounded hover:bg-blue-600 disabled:opacity-50"
|
||||
>
|
||||
{isLoading ? 'Logging in...' : 'Login'}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
34
apps/web-client/src/app/page.tsx
Normal file
34
apps/web-client/src/app/page.tsx
Normal file
@@ -0,0 +1,34 @@
|
||||
'use client';
|
||||
|
||||
import { useAuthStore } from '@/stores/auth.store';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
export default function Home() {
|
||||
const { user, isAuthenticated, isLoading, fetchUser } = useAuthStore();
|
||||
|
||||
useEffect(() => {
|
||||
if (!isAuthenticated && !isLoading) {
|
||||
fetchUser();
|
||||
}
|
||||
}, [isAuthenticated, isLoading, fetchUser]);
|
||||
|
||||
if (isLoading) {
|
||||
return <div className="p-8">Loading...</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<main className="min-h-screen p-8">
|
||||
<h1 className="text-4xl font-bold mb-4">GoodGo Platform</h1>
|
||||
{isAuthenticated && user ? (
|
||||
<div>
|
||||
<p>Welcome, {user.email}!</p>
|
||||
<p>Role: {user.role}</p>
|
||||
</div>
|
||||
) : (
|
||||
<div>
|
||||
<p>Please log in to continue.</p>
|
||||
</div>
|
||||
)}
|
||||
</main>
|
||||
);
|
||||
}
|
||||
45
apps/web-client/src/services/api/auth.api.ts
Normal file
45
apps/web-client/src/services/api/auth.api.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import { apiClient } from './client';
|
||||
import { LoginDto, RegisterDto, AuthResponse, ApiResponse, UserResponse } from '@goodgo/types';
|
||||
|
||||
export const authApi = {
|
||||
register: async (data: RegisterDto): Promise<ApiResponse<AuthResponse>> => {
|
||||
return apiClient.post('/auth/register', data);
|
||||
},
|
||||
|
||||
login: async (data: LoginDto): Promise<ApiResponse<AuthResponse>> => {
|
||||
const response = await apiClient.post('/auth/login', data);
|
||||
if (response.success && response.data) {
|
||||
apiClient.setAuthToken(response.data.accessToken);
|
||||
if (typeof window !== 'undefined') {
|
||||
localStorage.setItem('refreshToken', response.data.refreshToken);
|
||||
}
|
||||
}
|
||||
return response;
|
||||
},
|
||||
|
||||
logout: async (): Promise<ApiResponse> => {
|
||||
const refreshToken = typeof window !== 'undefined' ? localStorage.getItem('refreshToken') : null;
|
||||
const response = await apiClient.post('/auth/logout', { refreshToken });
|
||||
apiClient.removeAuthToken();
|
||||
if (typeof window !== 'undefined') {
|
||||
localStorage.removeItem('refreshToken');
|
||||
}
|
||||
return response;
|
||||
},
|
||||
|
||||
refreshToken: async (refreshToken: string): Promise<ApiResponse<{ accessToken: string }>> => {
|
||||
const response = await apiClient.post('/auth/refresh', { refreshToken });
|
||||
if (response.success && response.data) {
|
||||
apiClient.setAuthToken(response.data.accessToken);
|
||||
}
|
||||
return response;
|
||||
},
|
||||
|
||||
getMe: async (): Promise<ApiResponse<UserResponse>> => {
|
||||
return apiClient.get('/users/me');
|
||||
},
|
||||
|
||||
changePassword: async (currentPassword: string, newPassword: string): Promise<ApiResponse> => {
|
||||
return apiClient.put('/auth/password', { currentPassword, newPassword });
|
||||
},
|
||||
};
|
||||
8
apps/web-client/src/services/api/client.ts
Normal file
8
apps/web-client/src/services/api/client.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import { createHttpClient } from '@goodgo/http-client';
|
||||
|
||||
const API_URL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost/api/v1';
|
||||
|
||||
export const apiClient = createHttpClient({
|
||||
baseURL: API_URL,
|
||||
timeout: 30000,
|
||||
});
|
||||
103
apps/web-client/src/stores/auth.store.ts
Normal file
103
apps/web-client/src/stores/auth.store.ts
Normal file
@@ -0,0 +1,103 @@
|
||||
import { create } from 'zustand';
|
||||
import { persist } from 'zustand/middleware';
|
||||
import { UserResponse } from '@goodgo/types';
|
||||
import { authApi } from '../services/api/auth.api';
|
||||
|
||||
interface AuthState {
|
||||
user: UserResponse | null;
|
||||
isAuthenticated: boolean;
|
||||
isLoading: boolean;
|
||||
login: (email: string, password: string) => Promise<void>;
|
||||
register: (email: string, password: string, confirmPassword: string) => Promise<void>;
|
||||
logout: () => Promise<void>;
|
||||
fetchUser: () => Promise<void>;
|
||||
}
|
||||
|
||||
export const useAuthStore = create<AuthState>()(
|
||||
persist(
|
||||
(set) => ({
|
||||
user: null,
|
||||
isAuthenticated: false,
|
||||
isLoading: false,
|
||||
|
||||
login: async (email: string, password: string) => {
|
||||
set({ isLoading: true });
|
||||
try {
|
||||
const response = await authApi.login({ email, password });
|
||||
if (response.success && response.data) {
|
||||
set({
|
||||
user: response.data.user,
|
||||
isAuthenticated: true,
|
||||
isLoading: false,
|
||||
});
|
||||
} else {
|
||||
throw new Error(response.error?.message || 'Login failed');
|
||||
}
|
||||
} catch (error) {
|
||||
set({ isLoading: false });
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
register: async (email: string, password: string, confirmPassword: string) => {
|
||||
set({ isLoading: true });
|
||||
try {
|
||||
const response = await authApi.register({ email, password, confirmPassword });
|
||||
if (response.success && response.data) {
|
||||
set({
|
||||
user: response.data.user,
|
||||
isAuthenticated: true,
|
||||
isLoading: false,
|
||||
});
|
||||
} else {
|
||||
throw new Error(response.error?.message || 'Registration failed');
|
||||
}
|
||||
} catch (error) {
|
||||
set({ isLoading: false });
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
logout: async () => {
|
||||
try {
|
||||
await authApi.logout();
|
||||
} finally {
|
||||
set({
|
||||
user: null,
|
||||
isAuthenticated: false,
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
fetchUser: async () => {
|
||||
set({ isLoading: true });
|
||||
try {
|
||||
const response = await authApi.getMe();
|
||||
if (response.success && response.data) {
|
||||
set({
|
||||
user: response.data,
|
||||
isAuthenticated: true,
|
||||
isLoading: false,
|
||||
});
|
||||
} else {
|
||||
set({
|
||||
user: null,
|
||||
isAuthenticated: false,
|
||||
isLoading: false,
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
set({
|
||||
user: null,
|
||||
isAuthenticated: false,
|
||||
isLoading: false,
|
||||
});
|
||||
}
|
||||
},
|
||||
}),
|
||||
{
|
||||
name: 'auth-storage',
|
||||
partialize: (state) => ({ user: state.user, isAuthenticated: state.isAuthenticated }),
|
||||
}
|
||||
)
|
||||
);
|
||||
12
apps/web-client/tailwind.config.js
Normal file
12
apps/web-client/tailwind.config.js
Normal file
@@ -0,0 +1,12 @@
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
module.exports = {
|
||||
content: [
|
||||
'./src/pages/**/*.{js,ts,jsx,tsx,mdx}',
|
||||
'./src/components/**/*.{js,ts,jsx,tsx,mdx}',
|
||||
'./src/app/**/*.{js,ts,jsx,tsx,mdx}',
|
||||
],
|
||||
theme: {
|
||||
extend: {},
|
||||
},
|
||||
plugins: [],
|
||||
};
|
||||
21
apps/web-client/tsconfig.json
Normal file
21
apps/web-client/tsconfig.json
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"extends": "@goodgo/tsconfig/nextjs.json",
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": [
|
||||
"./src/*"
|
||||
]
|
||||
},
|
||||
"isolatedModules": true
|
||||
},
|
||||
"include": [
|
||||
"next-env.d.ts",
|
||||
"**/*.ts",
|
||||
"**/*.tsx",
|
||||
".next/types/**/*.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules"
|
||||
]
|
||||
}
|
||||
81
deployments/local/README.md
Normal file
81
deployments/local/README.md
Normal file
@@ -0,0 +1,81 @@
|
||||
# Local Development Setup
|
||||
|
||||
Docker Compose configuration for local development.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Docker & Docker Compose installed
|
||||
- Neon account (https://neon.tech) - for database
|
||||
- Ports available: 80, 6379, 5001, 3000, 3001, 8080
|
||||
|
||||
## Initial Setup
|
||||
|
||||
### 1. Setup Neon Database
|
||||
|
||||
```bash
|
||||
# Run setup script
|
||||
./scripts/db/setup-neon.sh
|
||||
|
||||
# Or manually:
|
||||
# 1. Create Neon project at https://neon.tech
|
||||
# 2. Get connection string from main branch
|
||||
# 3. Create deployments/local/.env.local:
|
||||
# DATABASE_URL=postgresql://user:pass@ep-xxx.region.neon.tech/dbname?sslmode=require&pgbouncer=true
|
||||
```
|
||||
|
||||
See [Neon Setup Guide](../../infra/databases/neon/README.md) for details.
|
||||
|
||||
### 2. Start Services
|
||||
|
||||
```bash
|
||||
# Start infrastructure (Redis, Traefik - no PostgreSQL needed)
|
||||
docker-compose up -d
|
||||
|
||||
# Run migrations
|
||||
./scripts/db/migrate.sh auth-service dev
|
||||
|
||||
# Seed database (optional)
|
||||
./scripts/db/seed.sh auth-service
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
# Start all services
|
||||
docker-compose up -d
|
||||
|
||||
# View logs
|
||||
docker-compose logs -f
|
||||
|
||||
# Stop all services
|
||||
docker-compose down
|
||||
|
||||
# Stop and remove volumes (clean slate)
|
||||
docker-compose down -v
|
||||
```
|
||||
|
||||
## Services
|
||||
|
||||
- **Neon Database**: Cloud-hosted (no local container)
|
||||
- **Redis**: `localhost:6379`
|
||||
- **Auth Service**: `localhost:5001`
|
||||
- **Web Admin**: `http://localhost:3000` or `http://admin.localhost`
|
||||
- **Web Client**: `http://localhost:3001` or `http://localhost`
|
||||
- **Traefik Dashboard**: `http://localhost:8080`
|
||||
|
||||
## Access
|
||||
|
||||
- API Gateway: `http://localhost/api/v1`
|
||||
- Web Admin: `http://admin.localhost` (via Traefik) or `http://localhost:3000` (direct)
|
||||
- Web Client: `http://localhost` (via Traefik) or `http://localhost:3001` (direct)
|
||||
- Traefik Dashboard: `http://localhost:8080`
|
||||
|
||||
## Environment Variables
|
||||
|
||||
Copy `env.local.example` to `.env.local` and add your Neon DATABASE_URL:
|
||||
|
||||
```bash
|
||||
DATABASE_URL=postgresql://user:pass@ep-xxx.region.neon.tech/dbname?sslmode=require&pgbouncer=true
|
||||
```
|
||||
|
||||
**Note**: PostgreSQL is not included in Docker Compose. All environments use Neon database.
|
||||
120
deployments/local/docker-compose.yml
Normal file
120
deployments/local/docker-compose.yml
Normal file
@@ -0,0 +1,120 @@
|
||||
version: '3.8'
|
||||
|
||||
# NOTE: This setup uses Neon PostgreSQL database
|
||||
# Setup Neon database URL in .env.local file
|
||||
# See infra/databases/neon/README.md for setup instructions
|
||||
|
||||
services:
|
||||
redis:
|
||||
image: redis:7-alpine
|
||||
container_name: redis-cache-local
|
||||
command: redis-server /etc/redis/redis.conf
|
||||
ports:
|
||||
- "6379:6379"
|
||||
volumes:
|
||||
- redis_data:/data
|
||||
- ../../infra/databases/redis/redis.conf:/etc/redis/redis.conf
|
||||
healthcheck:
|
||||
test: ["CMD", "redis-cli", "ping"]
|
||||
interval: 5s
|
||||
timeout: 3s
|
||||
retries: 5
|
||||
networks:
|
||||
- microservices-network
|
||||
|
||||
auth-service:
|
||||
build:
|
||||
context: ../..
|
||||
dockerfile: services/auth-service/Dockerfile
|
||||
container_name: auth-service-local
|
||||
env_file:
|
||||
- .env.local
|
||||
environment:
|
||||
- NODE_ENV=development
|
||||
- PORT=5001
|
||||
# DATABASE_URL should be set in .env.local (Neon database URL)
|
||||
- REDIS_HOST=redis
|
||||
- REDIS_PORT=6379
|
||||
- JWT_SECRET=${JWT_SECRET:-dev-jwt-secret-change-in-production}
|
||||
- JWT_EXPIRES_IN=15m
|
||||
- JWT_REFRESH_SECRET=${JWT_REFRESH_SECRET:-dev-refresh-secret-change-in-production}
|
||||
- JWT_REFRESH_EXPIRES_IN=7d
|
||||
- CORS_ORIGIN=http://localhost:3000,http://localhost:3001
|
||||
- LOG_LEVEL=debug
|
||||
- SERVICE_NAME=auth-service
|
||||
ports:
|
||||
- "5001:5001"
|
||||
depends_on:
|
||||
redis:
|
||||
condition: service_healthy
|
||||
networks:
|
||||
- microservices-network
|
||||
|
||||
web-admin:
|
||||
build:
|
||||
context: ../..
|
||||
dockerfile: apps/web-admin/Dockerfile
|
||||
container_name: web-admin-local
|
||||
environment:
|
||||
- NODE_ENV=development
|
||||
- NEXT_PUBLIC_API_URL=http://localhost/api/v1
|
||||
ports:
|
||||
- "3000:3000"
|
||||
depends_on:
|
||||
- auth-service
|
||||
networks:
|
||||
- microservices-network
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.web-admin.rule=Host(`admin.localhost`)"
|
||||
- "traefik.http.routers.web-admin.entrypoints=web"
|
||||
- "traefik.http.services.web-admin.loadbalancer.server.port=3000"
|
||||
|
||||
web-client:
|
||||
build:
|
||||
context: ../..
|
||||
dockerfile: apps/web-client/Dockerfile
|
||||
container_name: web-client-local
|
||||
environment:
|
||||
- NODE_ENV=development
|
||||
- NEXT_PUBLIC_API_URL=http://localhost/api/v1
|
||||
ports:
|
||||
- "3001:3000"
|
||||
depends_on:
|
||||
- auth-service
|
||||
networks:
|
||||
- microservices-network
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.web-client.rule=Host(`localhost`)"
|
||||
- "traefik.http.routers.web-client.entrypoints=web"
|
||||
- "traefik.http.services.web-client.loadbalancer.server.port=3000"
|
||||
|
||||
traefik:
|
||||
image: traefik:v2.10
|
||||
container_name: traefik-local
|
||||
command:
|
||||
- "--api.insecure=true"
|
||||
- "--providers.docker=true"
|
||||
- "--providers.docker.exposedbydefault=false"
|
||||
- "--entrypoints.web.address=:80"
|
||||
- "--log.level=INFO"
|
||||
ports:
|
||||
- "80:80"
|
||||
- "8080:8080"
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||
- ../../infra/traefik:/etc/traefik
|
||||
networks:
|
||||
- microservices-network
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.traefik.rule=Host(`traefik.localhost`)"
|
||||
- "traefik.http.routers.traefik.entrypoints=web"
|
||||
|
||||
volumes:
|
||||
redis_data:
|
||||
|
||||
networks:
|
||||
microservices-network:
|
||||
driver: bridge
|
||||
54
deployments/local/env.local.example
Normal file
54
deployments/local/env.local.example
Normal file
@@ -0,0 +1,54 @@
|
||||
# Local Development Environment Variables (Shared Configs)
|
||||
# Shared Environment Variables - Shared across all services
|
||||
# Copy this file to .env.local and fill in your values
|
||||
#
|
||||
# Note: Service-specific configs (DATABASE_URL, PORT) should be in services/<service-name>/.env.local
|
||||
|
||||
# =============================================================================
|
||||
# SHARED SECRETS - Must be same across all services for JWT token verification
|
||||
# =============================================================================
|
||||
JWT_SECRET=dev-jwt-secret-change-in-production-min-32-chars
|
||||
JWT_REFRESH_SECRET=dev-refresh-secret-change-in-production-min-32-chars
|
||||
JWT_EXPIRES_IN=15m
|
||||
JWT_REFRESH_EXPIRES_IN=7d
|
||||
|
||||
# =============================================================================
|
||||
# SHARED INFRASTRUCTURE - Redis, Traefik
|
||||
# =============================================================================
|
||||
# Redis (Docker container name when using docker-compose)
|
||||
REDIS_HOST=redis
|
||||
REDIS_PORT=6379
|
||||
REDIS_PASSWORD=
|
||||
|
||||
# =============================================================================
|
||||
# COMMON CONFIGURATION
|
||||
# =============================================================================
|
||||
NODE_ENV=development
|
||||
LOG_LEVEL=debug
|
||||
|
||||
# CORS - Allowed origins for all services
|
||||
CORS_ORIGIN=http://localhost:3000,http://localhost:3001,http://admin.localhost
|
||||
|
||||
# =============================================================================
|
||||
# MONITORING & TRACING (Optional)
|
||||
# =============================================================================
|
||||
TRACING_ENABLED=false
|
||||
JAEGER_ENDPOINT=http://jaeger:14268/api/traces
|
||||
|
||||
# =============================================================================
|
||||
# EXTERNAL SERVICES (Optional)
|
||||
# =============================================================================
|
||||
EMAIL_FROM=noreply@goodgo.vn
|
||||
|
||||
# =============================================================================
|
||||
# NOTES
|
||||
# =============================================================================
|
||||
# - Each service should have its own .env.local for service-specific configs:
|
||||
# * DATABASE_URL (each service has its own database)
|
||||
# * PORT (each service has different port)
|
||||
# * SERVICE_NAME
|
||||
# * REDIS_HOST=localhost (override when running native, Redis in Docker)
|
||||
#
|
||||
# - Get Neon database URLs from: https://console.neon.tech
|
||||
# - Create separate databases for each service (microservices pattern)
|
||||
# - JWT secrets MUST be identical across all services
|
||||
19
deployments/production/env.production.example
Normal file
19
deployments/production/env.production.example
Normal file
@@ -0,0 +1,19 @@
|
||||
# Production Environment Variables
|
||||
# Use these values to create Kubernetes secrets
|
||||
|
||||
# Neon Database (Production branch)
|
||||
DATABASE_URL=postgresql://user:password@ep-xxx.region.neon.tech/dbname?sslmode=require&pgbouncer=true
|
||||
|
||||
# JWT Secrets (use strong random strings - min 32 chars)
|
||||
JWT_SECRET=your-production-jwt-secret-min-32-chars
|
||||
JWT_REFRESH_SECRET=your-production-refresh-secret-min-32-chars
|
||||
|
||||
# Redis
|
||||
REDIS_HOST=redis-service
|
||||
REDIS_PORT=6379
|
||||
|
||||
# Notes:
|
||||
# - Store these in Kubernetes secrets or GitHub Secrets
|
||||
# - Never commit actual values to Git
|
||||
# - Use kubectl create secret or GitHub Secrets for CI/CD
|
||||
# - Rotate secrets regularly
|
||||
91
deployments/production/kubernetes/auth-service.yaml
Normal file
91
deployments/production/kubernetes/auth-service.yaml
Normal file
@@ -0,0 +1,91 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: auth-service
|
||||
namespace: production
|
||||
spec:
|
||||
replicas: 3
|
||||
selector:
|
||||
matchLabels:
|
||||
app: auth-service
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: auth-service
|
||||
spec:
|
||||
containers:
|
||||
- name: auth-service
|
||||
image: goodgo/auth-service:latest
|
||||
imagePullPolicy: Always
|
||||
ports:
|
||||
- containerPort: 5001
|
||||
envFrom:
|
||||
- configMapRef:
|
||||
name: auth-service-config
|
||||
- secretRef:
|
||||
name: auth-service-secrets
|
||||
resources:
|
||||
requests:
|
||||
memory: "512Mi"
|
||||
cpu: "500m"
|
||||
limits:
|
||||
memory: "1Gi"
|
||||
cpu: "1000m"
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /health/live
|
||||
port: 5001
|
||||
initialDelaySeconds: 30
|
||||
periodSeconds: 10
|
||||
timeoutSeconds: 5
|
||||
failureThreshold: 3
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /health/ready
|
||||
port: 5001
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 5
|
||||
timeoutSeconds: 3
|
||||
failureThreshold: 3
|
||||
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: auth-service
|
||||
namespace: production
|
||||
spec:
|
||||
selector:
|
||||
app: auth-service
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 5001
|
||||
targetPort: 5001
|
||||
type: ClusterIP
|
||||
|
||||
---
|
||||
apiVersion: autoscaling/v2
|
||||
kind: HorizontalPodAutoscaler
|
||||
metadata:
|
||||
name: auth-service-hpa
|
||||
namespace: production
|
||||
spec:
|
||||
scaleTargetRef:
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
name: auth-service
|
||||
minReplicas: 3
|
||||
maxReplicas: 10
|
||||
metrics:
|
||||
- type: Resource
|
||||
resource:
|
||||
name: cpu
|
||||
target:
|
||||
type: Utilization
|
||||
averageUtilization: 70
|
||||
- type: Resource
|
||||
resource:
|
||||
name: memory
|
||||
target:
|
||||
type: Utilization
|
||||
averageUtilization: 80
|
||||
15
deployments/production/kubernetes/configmap.yaml
Normal file
15
deployments/production/kubernetes/configmap.yaml
Normal file
@@ -0,0 +1,15 @@
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: auth-service-config
|
||||
namespace: production
|
||||
data:
|
||||
NODE_ENV: "production"
|
||||
PORT: "5001"
|
||||
API_VERSION: "v1"
|
||||
CORS_ORIGIN: "https://goodgo.vn"
|
||||
LOG_LEVEL: "warn"
|
||||
SERVICE_NAME: "auth-service"
|
||||
TRACING_ENABLED: "true"
|
||||
# Note: DATABASE_URL is stored in secrets (auth-service-secrets)
|
||||
# DATABASE_URL should point to Neon production branch
|
||||
32
deployments/production/kubernetes/ingress.yaml
Normal file
32
deployments/production/kubernetes/ingress.yaml
Normal file
@@ -0,0 +1,32 @@
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: api-ingress
|
||||
namespace: production
|
||||
annotations:
|
||||
traefik.ingress.kubernetes.io/rule-type: PathPrefix
|
||||
cert-manager.io/cluster-issuer: "letsencrypt-prod"
|
||||
spec:
|
||||
ingressClassName: traefik
|
||||
tls:
|
||||
- hosts:
|
||||
- api.goodgo.vn
|
||||
secretName: api-tls-cert
|
||||
rules:
|
||||
- host: api.goodgo.vn
|
||||
http:
|
||||
paths:
|
||||
- path: /api/v1/auth
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: auth-service
|
||||
port:
|
||||
number: 5001
|
||||
- path: /api/v1/users
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: auth-service
|
||||
port:
|
||||
number: 5001
|
||||
34
deployments/production/kubernetes/secrets.yaml.example
Normal file
34
deployments/production/kubernetes/secrets.yaml.example
Normal file
@@ -0,0 +1,34 @@
|
||||
# Kubernetes Secrets Template for Production
|
||||
# DO NOT commit actual secrets to Git
|
||||
# Use this as a template to create secrets
|
||||
|
||||
# Create secret using kubectl:
|
||||
# kubectl create secret generic auth-service-secrets \
|
||||
# --from-literal=database-url='postgresql://user:pass@ep-xxx.region.neon.tech/dbname?sslmode=require&pgbouncer=true' \
|
||||
# --from-literal=jwt-secret='your-production-jwt-secret-min-32-chars' \
|
||||
# --from-literal=jwt-refresh-secret='your-production-refresh-secret-min-32-chars' \
|
||||
# --from-literal=redis-password='' \
|
||||
# -n production
|
||||
|
||||
# Or use GitHub Secrets in CI/CD:
|
||||
# - NEON_DATABASE_URL_PRODUCTION
|
||||
# - JWT_SECRET_PRODUCTION
|
||||
# - JWT_REFRESH_SECRET_PRODUCTION
|
||||
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: auth-service-secrets
|
||||
namespace: production
|
||||
type: Opaque
|
||||
stringData:
|
||||
# Neon Database URL (Production branch)
|
||||
# Format: postgresql://user:password@ep-xxx.region.neon.tech/dbname?sslmode=require&pgbouncer=true
|
||||
database-url: "postgresql://user:password@ep-xxx.region.neon.tech/dbname?sslmode=require&pgbouncer=true"
|
||||
|
||||
# JWT Secrets (use strong random strings, min 32 characters)
|
||||
jwt-secret: "your-production-jwt-secret-min-32-chars"
|
||||
jwt-refresh-secret: "your-production-refresh-secret-min-32-chars"
|
||||
|
||||
# Redis (if password protected)
|
||||
redis-password: ""
|
||||
18
deployments/staging/env.staging.example
Normal file
18
deployments/staging/env.staging.example
Normal file
@@ -0,0 +1,18 @@
|
||||
# Staging Environment Variables
|
||||
# Use these values to create Kubernetes secrets
|
||||
|
||||
# Neon Database (Staging branch)
|
||||
DATABASE_URL=postgresql://user:password@ep-xxx.region.neon.tech/dbname?sslmode=require&pgbouncer=true
|
||||
|
||||
# JWT Secrets (use strong random strings)
|
||||
JWT_SECRET=your-staging-jwt-secret-min-32-chars
|
||||
JWT_REFRESH_SECRET=your-staging-refresh-secret-min-32-chars
|
||||
|
||||
# Redis
|
||||
REDIS_HOST=redis-service
|
||||
REDIS_PORT=6379
|
||||
|
||||
# Notes:
|
||||
# - Store these in Kubernetes secrets or GitHub Secrets
|
||||
# - Never commit actual values to Git
|
||||
# - Use kubectl create secret or GitHub Secrets for CI/CD
|
||||
69
deployments/staging/kubernetes/auth-service.yaml
Normal file
69
deployments/staging/kubernetes/auth-service.yaml
Normal file
@@ -0,0 +1,69 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: auth-service
|
||||
namespace: staging
|
||||
spec:
|
||||
replicas: 2
|
||||
selector:
|
||||
matchLabels:
|
||||
app: auth-service
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: auth-service
|
||||
spec:
|
||||
containers:
|
||||
- name: auth-service
|
||||
image: goodgo/auth-service:latest
|
||||
ports:
|
||||
- containerPort: 5001
|
||||
env:
|
||||
- name: NODE_ENV
|
||||
value: "staging"
|
||||
- name: DATABASE_URL
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: auth-service-secrets
|
||||
key: database-url
|
||||
- name: JWT_SECRET
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: auth-service-secrets
|
||||
key: jwt-secret
|
||||
- name: REDIS_HOST
|
||||
value: "redis-service"
|
||||
resources:
|
||||
requests:
|
||||
memory: "256Mi"
|
||||
cpu: "250m"
|
||||
limits:
|
||||
memory: "512Mi"
|
||||
cpu: "500m"
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /health/live
|
||||
port: 5001
|
||||
initialDelaySeconds: 30
|
||||
periodSeconds: 10
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /health/ready
|
||||
port: 5001
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 5
|
||||
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: auth-service
|
||||
namespace: staging
|
||||
spec:
|
||||
selector:
|
||||
app: auth-service
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 5001
|
||||
targetPort: 5001
|
||||
type: ClusterIP
|
||||
14
deployments/staging/kubernetes/configmap.yaml
Normal file
14
deployments/staging/kubernetes/configmap.yaml
Normal file
@@ -0,0 +1,14 @@
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: auth-service-config
|
||||
namespace: staging
|
||||
data:
|
||||
NODE_ENV: "staging"
|
||||
PORT: "5001"
|
||||
API_VERSION: "v1"
|
||||
CORS_ORIGIN: "https://staging.goodgo.vn"
|
||||
LOG_LEVEL: "info"
|
||||
SERVICE_NAME: "auth-service"
|
||||
# Note: DATABASE_URL is stored in secrets (auth-service-secrets)
|
||||
# DATABASE_URL should point to Neon staging branch
|
||||
27
deployments/staging/kubernetes/ingress.yaml
Normal file
27
deployments/staging/kubernetes/ingress.yaml
Normal file
@@ -0,0 +1,27 @@
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: api-ingress
|
||||
namespace: staging
|
||||
annotations:
|
||||
traefik.ingress.kubernetes.io/rule-type: PathPrefix
|
||||
spec:
|
||||
ingressClassName: traefik
|
||||
rules:
|
||||
- host: api.staging.goodgo.vn
|
||||
http:
|
||||
paths:
|
||||
- path: /api/v1/auth
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: auth-service
|
||||
port:
|
||||
number: 5001
|
||||
- path: /api/v1/users
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: auth-service
|
||||
port:
|
||||
number: 5001
|
||||
34
deployments/staging/kubernetes/secrets.yaml.example
Normal file
34
deployments/staging/kubernetes/secrets.yaml.example
Normal file
@@ -0,0 +1,34 @@
|
||||
# Kubernetes Secrets Template for Staging
|
||||
# DO NOT commit actual secrets to Git
|
||||
# Use this as a template to create secrets
|
||||
|
||||
# Create secret using kubectl:
|
||||
# kubectl create secret generic auth-service-secrets \
|
||||
# --from-literal=database-url='postgresql://user:pass@ep-xxx.region.neon.tech/dbname?sslmode=require&pgbouncer=true' \
|
||||
# --from-literal=jwt-secret='your-staging-jwt-secret-min-32-chars' \
|
||||
# --from-literal=jwt-refresh-secret='your-staging-refresh-secret-min-32-chars' \
|
||||
# --from-literal=redis-password='' \
|
||||
# -n staging
|
||||
|
||||
# Or use GitHub Secrets in CI/CD:
|
||||
# - NEON_DATABASE_URL_STAGING
|
||||
# - JWT_SECRET_STAGING
|
||||
# - JWT_REFRESH_SECRET_STAGING
|
||||
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: auth-service-secrets
|
||||
namespace: staging
|
||||
type: Opaque
|
||||
stringData:
|
||||
# Neon Database URL (Staging branch)
|
||||
# Format: postgresql://user:password@ep-xxx.region.neon.tech/dbname?sslmode=require&pgbouncer=true
|
||||
database-url: "postgresql://user:password@ep-xxx.region.neon.tech/dbname?sslmode=require&pgbouncer=true"
|
||||
|
||||
# JWT Secrets (use strong random strings, min 32 characters)
|
||||
jwt-secret: "your-staging-jwt-secret-min-32-chars"
|
||||
jwt-refresh-secret: "your-staging-refresh-secret-min-32-chars"
|
||||
|
||||
# Redis (if password protected)
|
||||
redis-password: ""
|
||||
45
docs/README.md
Normal file
45
docs/README.md
Normal file
@@ -0,0 +1,45 @@
|
||||
# Documentation
|
||||
|
||||
This directory contains documentation for the GoodGo Microservices Platform, organized by language.
|
||||
|
||||
## Structure
|
||||
|
||||
```
|
||||
docs/
|
||||
├── en/ # English documentation
|
||||
│ ├── api/
|
||||
│ ├── architecture/
|
||||
│ ├── guides/
|
||||
│ ├── onboarding/
|
||||
│ └── runbooks/
|
||||
├── vi/ # Vietnamese documentation (Tiếng Việt)
|
||||
│ ├── api/
|
||||
│ ├── architecture/
|
||||
│ ├── guides/
|
||||
│ ├── onboarding/
|
||||
│ └── runbooks/
|
||||
└── README.md # This file
|
||||
```
|
||||
|
||||
## Available Documentation
|
||||
|
||||
### English (`/en`)
|
||||
- **API**: OpenAPI specifications
|
||||
- **Architecture**: System design and service communication patterns
|
||||
- **Guides**: Development, deployment, getting started, troubleshooting
|
||||
- **Onboarding**: New developer guide
|
||||
- **Runbooks**: Incident response and rollback procedures
|
||||
|
||||
### Vietnamese (`/vi`)
|
||||
- **API**: OpenAPI specifications
|
||||
- **Architecture**: Thiết kế hệ thống và các mẫu giao tiếp service
|
||||
- **Guides**: Development, deployment, bắt đầu, xử lý sự cố
|
||||
- **Onboarding**: Hướng dẫn cho developer mới
|
||||
- **Runbooks**: Phản ứng sự cố và quy trình rollback
|
||||
|
||||
## Contributing
|
||||
|
||||
When adding new documentation:
|
||||
1. Add the English version to `/en`
|
||||
2. Add the Vietnamese translation to `/vi`
|
||||
3. Keep both versions in sync
|
||||
102
docs/en/api/openapi/auth-service.yaml
Normal file
102
docs/en/api/openapi/auth-service.yaml
Normal file
@@ -0,0 +1,102 @@
|
||||
openapi: 3.0.0
|
||||
info:
|
||||
title: Auth Service API
|
||||
version: 1.0.0
|
||||
description: Authentication and Authorization Service API
|
||||
|
||||
servers:
|
||||
- url: http://localhost/api/v1
|
||||
description: Local development
|
||||
- url: https://api.goodgo.vn/api/v1
|
||||
description: Production
|
||||
|
||||
paths:
|
||||
/auth/register:
|
||||
post:
|
||||
summary: Register new user
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
email:
|
||||
type: string
|
||||
format: email
|
||||
password:
|
||||
type: string
|
||||
minLength: 6
|
||||
confirmPassword:
|
||||
type: string
|
||||
responses:
|
||||
'201':
|
||||
description: User registered successfully
|
||||
'400':
|
||||
description: Validation error
|
||||
|
||||
/auth/login:
|
||||
post:
|
||||
summary: Login user
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
email:
|
||||
type: string
|
||||
format: email
|
||||
password:
|
||||
type: string
|
||||
responses:
|
||||
'200':
|
||||
description: Login successful
|
||||
'401':
|
||||
description: Invalid credentials
|
||||
|
||||
/auth/logout:
|
||||
post:
|
||||
summary: Logout user
|
||||
security:
|
||||
- bearerAuth: []
|
||||
responses:
|
||||
'200':
|
||||
description: Logout successful
|
||||
|
||||
/auth/refresh:
|
||||
post:
|
||||
summary: Refresh access token
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
refreshToken:
|
||||
type: string
|
||||
responses:
|
||||
'200':
|
||||
description: Token refreshed
|
||||
'401':
|
||||
description: Invalid refresh token
|
||||
|
||||
/users/me:
|
||||
get:
|
||||
summary: Get current user
|
||||
security:
|
||||
- bearerAuth: []
|
||||
responses:
|
||||
'200':
|
||||
description: User information
|
||||
'401':
|
||||
description: Unauthorized
|
||||
|
||||
components:
|
||||
securitySchemes:
|
||||
bearerAuth:
|
||||
type: http
|
||||
scheme: bearer
|
||||
bearerFormat: JWT
|
||||
58
docs/en/architecture/service-communication.md
Normal file
58
docs/en/architecture/service-communication.md
Normal file
@@ -0,0 +1,58 @@
|
||||
# Service Communication
|
||||
|
||||
## Communication Patterns
|
||||
|
||||
### Synchronous Communication (HTTP/REST)
|
||||
|
||||
Services communicate synchronously via HTTP REST APIs through Traefik API Gateway.
|
||||
|
||||
**Example:**
|
||||
```typescript
|
||||
// Web App -> Auth Service
|
||||
const response = await fetch('http://api.goodgo.vn/api/v1/auth/login', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ email, password }),
|
||||
});
|
||||
```
|
||||
|
||||
### Service-to-Service Communication
|
||||
|
||||
Services can communicate directly via internal network:
|
||||
|
||||
```typescript
|
||||
// Auth Service -> Notification Service (future)
|
||||
const response = await fetch('http://notification-service:5003/api/v1/notifications', {
|
||||
method: 'POST',
|
||||
headers: { 'X-Service-Auth': process.env.INTERNAL_API_KEY },
|
||||
body: JSON.stringify({ userId, message }),
|
||||
});
|
||||
```
|
||||
|
||||
## API Gateway Routing
|
||||
|
||||
Traefik routes requests based on:
|
||||
- Host header (`api.goodgo.vn`)
|
||||
- Path prefix (`/api/v1/auth`)
|
||||
|
||||
## Error Handling
|
||||
|
||||
All services follow consistent error response format:
|
||||
|
||||
```json
|
||||
{
|
||||
"success": false,
|
||||
"error": {
|
||||
"code": "AUTH_001",
|
||||
"message": "Invalid credentials",
|
||||
"details": {}
|
||||
},
|
||||
"timestamp": "2024-01-01T00:00:00.000Z"
|
||||
}
|
||||
```
|
||||
|
||||
## Retry and Circuit Breaker
|
||||
|
||||
Future implementation:
|
||||
- Exponential backoff for retries
|
||||
- Circuit breaker pattern for fault tolerance
|
||||
- Fallback mechanisms
|
||||
81
docs/en/architecture/system-design.md
Normal file
81
docs/en/architecture/system-design.md
Normal file
@@ -0,0 +1,81 @@
|
||||
# System Design
|
||||
|
||||
## Overview
|
||||
|
||||
GoodGo Microservices Platform is built using a microservices architecture pattern with the following principles:
|
||||
|
||||
- **Service Independence**: Each service has its own database and can be deployed independently
|
||||
- **API Gateway**: Traefik handles routing, load balancing, and cross-cutting concerns
|
||||
- **Shared Libraries**: Common functionality is extracted into shared packages
|
||||
- **Infrastructure as Code**: All infrastructure configurations are versioned
|
||||
- **Observability**: Full monitoring, logging, and tracing capabilities
|
||||
|
||||
## Architecture Diagram
|
||||
|
||||
```
|
||||
┌─────────────┐ ┌─────────────┐
|
||||
│ Web App │ │ Mobile App │
|
||||
│ (Next.js) │ │ (React Native)
|
||||
└──────┬──────┘ └──────┬──────┘
|
||||
│ │
|
||||
└──────────┬────────┘
|
||||
│
|
||||
┌────────▼────────┐
|
||||
│ Traefik │
|
||||
│ (API Gateway) │
|
||||
└────────┬─────────┘
|
||||
│
|
||||
┌─────────────┼─────────────┐
|
||||
│ │ │
|
||||
┌───▼────┐ ┌───▼────┐ ┌───▼────┐
|
||||
│ Auth │ │ Future │ │ Future │
|
||||
│Service │ │Service │ │Service │
|
||||
└───┬────┘ └───┬────┘ └───┬────┘
|
||||
│ │ │
|
||||
└────────────┼────────────┘
|
||||
│
|
||||
┌────────────┼────────────┐
|
||||
│ │ │
|
||||
┌───▼────┐ ┌───▼────┐ ┌───▼────┐
|
||||
│Postgres│ │ Redis │ │Prometheus│
|
||||
└────────┘ └────────┘ └─────────┘
|
||||
```
|
||||
|
||||
## Components
|
||||
|
||||
### Frontend Layer
|
||||
- **Web App**: Next.js application with App Router
|
||||
- **Mobile App**: React Native application
|
||||
|
||||
### API Gateway
|
||||
- **Traefik**: Reverse proxy, load balancer, SSL termination
|
||||
|
||||
### Services Layer
|
||||
- **Auth Service**: Authentication and authorization
|
||||
- **Future Services**: Payment, Order, Notification, etc.
|
||||
|
||||
### Infrastructure Layer
|
||||
- **PostgreSQL**: Primary database
|
||||
- **Redis**: Caching and session storage
|
||||
- **Prometheus**: Metrics collection
|
||||
- **Grafana**: Metrics visualization
|
||||
- **Loki**: Log aggregation
|
||||
|
||||
## Communication Patterns
|
||||
|
||||
- **Synchronous**: HTTP/REST for request-response patterns
|
||||
- **Asynchronous**: Message queues (future implementation)
|
||||
- **Service Discovery**: Docker networking and Kubernetes DNS
|
||||
|
||||
## Data Management
|
||||
|
||||
- **Database per Service**: Each service owns its data
|
||||
- **API Composition**: Services expose APIs for data access
|
||||
- **Event Sourcing**: Future consideration for audit trails
|
||||
|
||||
## Security
|
||||
|
||||
- **Authentication**: JWT tokens with refresh token rotation
|
||||
- **Authorization**: Role-based access control (RBAC)
|
||||
- **Network Security**: TLS/SSL, rate limiting, CORS
|
||||
- **Secrets Management**: Environment variables, Kubernetes secrets
|
||||
106
docs/en/guides/deployment.md
Normal file
106
docs/en/guides/deployment.md
Normal file
@@ -0,0 +1,106 @@
|
||||
# Deployment Guide
|
||||
|
||||
## Database Setup (Neon)
|
||||
|
||||
All environments use **Neon PostgreSQL**. Setup once before deployment:
|
||||
|
||||
1. Create Neon project at https://neon.tech
|
||||
2. Create branches: `main` (dev), `staging`, `production`
|
||||
3. Get connection strings for each branch
|
||||
4. Configure in environment variables (see below)
|
||||
|
||||
See [Neon Setup Guide](../../infra/databases/neon/README.md) for details.
|
||||
|
||||
## Local Deployment
|
||||
|
||||
```bash
|
||||
# Setup Neon database URL
|
||||
cp deployments/local/env.local.example deployments/local/.env.local
|
||||
# Edit .env.local and add your Neon DATABASE_URL
|
||||
|
||||
# Start services (no PostgreSQL container needed)
|
||||
cd deployments/local
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
## Staging Deployment
|
||||
|
||||
### Prerequisites
|
||||
- Kubernetes cluster access
|
||||
- kubectl configured
|
||||
- KUBECONFIG set
|
||||
- Neon staging branch created
|
||||
- GitHub Secrets configured:
|
||||
- `NEON_DATABASE_URL_STAGING`
|
||||
- `KUBECONFIG_STAGING`
|
||||
|
||||
### Setup Secrets
|
||||
|
||||
```bash
|
||||
# Create Kubernetes secret
|
||||
kubectl create secret generic auth-service-secrets \
|
||||
--from-literal=database-url='postgresql://user:pass@ep-xxx.region.neon.tech/dbname?sslmode=require&pgbouncer=true' \
|
||||
--from-literal=jwt-secret='your-staging-jwt-secret' \
|
||||
--from-literal=jwt-refresh-secret='your-staging-refresh-secret' \
|
||||
-n staging
|
||||
```
|
||||
|
||||
### Deploy
|
||||
|
||||
```bash
|
||||
./scripts/deploy/deploy-staging.sh
|
||||
```
|
||||
|
||||
Or manually:
|
||||
```bash
|
||||
kubectl apply -f deployments/staging/kubernetes/
|
||||
```
|
||||
|
||||
**Note**: Migrations run automatically in CI/CD before deployment.
|
||||
|
||||
## Production Deployment
|
||||
|
||||
### Prerequisites
|
||||
- Production Kubernetes cluster
|
||||
- kubectl configured with production context
|
||||
- Neon production branch created
|
||||
- GitHub Secrets configured:
|
||||
- `NEON_DATABASE_URL_PRODUCTION`
|
||||
- `KUBECONFIG_PRODUCTION`
|
||||
|
||||
### Setup Secrets
|
||||
|
||||
```bash
|
||||
# Create Kubernetes secret
|
||||
kubectl create secret generic auth-service-secrets \
|
||||
--from-literal=database-url='postgresql://user:pass@ep-xxx.region.neon.tech/dbname?sslmode=require&pgbouncer=true' \
|
||||
--from-literal=jwt-secret='your-production-jwt-secret' \
|
||||
--from-literal=jwt-refresh-secret='your-production-refresh-secret' \
|
||||
-n production
|
||||
```
|
||||
|
||||
### Deploy
|
||||
|
||||
```bash
|
||||
./scripts/deploy/deploy-prod.sh
|
||||
```
|
||||
|
||||
**Note**: Migrations run automatically in CI/CD before deployment (with approval).
|
||||
|
||||
### Rollback
|
||||
|
||||
```bash
|
||||
kubectl rollout undo deployment/auth-service -n production
|
||||
```
|
||||
|
||||
## Health Checks
|
||||
|
||||
- Liveness: `GET /health/live`
|
||||
- Readiness: `GET /health/ready`
|
||||
- Health: `GET /health`
|
||||
|
||||
## Monitoring
|
||||
|
||||
- Prometheus: http://prometheus:9090
|
||||
- Grafana: http://grafana:3000
|
||||
- Traefik Dashboard: http://traefik:8080
|
||||
87
docs/en/guides/development.md
Normal file
87
docs/en/guides/development.md
Normal file
@@ -0,0 +1,87 @@
|
||||
# Development Guide
|
||||
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
├── apps/ # Frontend applications
|
||||
├── services/ # Backend microservices
|
||||
├── packages/ # Shared libraries
|
||||
├── infra/ # Infrastructure configs
|
||||
├── deployments/ # Deployment configs
|
||||
├── scripts/ # Automation scripts
|
||||
└── docs/ # Documentation
|
||||
```
|
||||
|
||||
## Development Workflow
|
||||
|
||||
### 1. Create a Feature Branch
|
||||
|
||||
```bash
|
||||
git checkout -b feature/my-feature
|
||||
```
|
||||
|
||||
### 2. Make Changes
|
||||
|
||||
- Write code following TypeScript strict mode
|
||||
- Add tests for new functionality
|
||||
- Update documentation if needed
|
||||
|
||||
### 3. Run Tests Locally
|
||||
|
||||
```bash
|
||||
# All tests
|
||||
pnpm test
|
||||
|
||||
# Specific service
|
||||
pnpm --filter @goodgo/auth-service test
|
||||
```
|
||||
|
||||
### 4. Lint and Format
|
||||
|
||||
```bash
|
||||
pnpm lint
|
||||
pnpm format
|
||||
```
|
||||
|
||||
### 5. Create Pull Request
|
||||
|
||||
- Push your branch
|
||||
- Create PR targeting `develop`
|
||||
- CI/CD will run automatically
|
||||
|
||||
## Adding a New Service
|
||||
|
||||
1. Use the template:
|
||||
```bash
|
||||
./scripts/utils/create-service.sh my-new-service
|
||||
```
|
||||
|
||||
2. Update service configuration
|
||||
3. Implement business logic
|
||||
4. Add tests
|
||||
5. Update documentation
|
||||
|
||||
## Adding a New Package
|
||||
|
||||
1. Create package in `packages/new-package`
|
||||
2. Add to workspace in `pnpm-workspace.yaml`
|
||||
3. Export from `index.ts`
|
||||
4. Add tests
|
||||
5. Document usage
|
||||
|
||||
## Database Migrations
|
||||
|
||||
```bash
|
||||
# Create migration
|
||||
cd services/auth-service
|
||||
pnpm prisma migrate dev --name add_new_field
|
||||
|
||||
# Apply migrations (production)
|
||||
pnpm prisma migrate deploy
|
||||
```
|
||||
|
||||
## Debugging
|
||||
|
||||
- Use logger from `@goodgo/logger`
|
||||
- Check Traefik logs: `docker logs traefik-local`
|
||||
- Check service logs: `./scripts/dev/logs.sh auth-service`
|
||||
81
docs/en/guides/getting-started.md
Normal file
81
docs/en/guides/getting-started.md
Normal file
@@ -0,0 +1,81 @@
|
||||
# Getting Started
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Node.js >= 20.0.0
|
||||
- PNPM >= 8.0.0
|
||||
- Docker & Docker Compose
|
||||
- Git
|
||||
- Neon account (https://neon.tech) - for database
|
||||
|
||||
## Initial Setup
|
||||
|
||||
1. **Clone the repository**
|
||||
```bash
|
||||
git clone <repository-url>
|
||||
cd Base
|
||||
```
|
||||
|
||||
2. **Setup Neon Database**
|
||||
```bash
|
||||
# Run setup script
|
||||
./scripts/db/setup-neon.sh
|
||||
|
||||
# Or manually:
|
||||
# 1. Create Neon project at https://neon.tech
|
||||
# 2. Create branches: main (dev), staging, production
|
||||
# 3. Get connection strings
|
||||
# 4. Update deployments/local/.env.local
|
||||
```
|
||||
|
||||
See [Neon Setup Guide](../../infra/databases/neon/README.md) for details.
|
||||
|
||||
3. **Initialize the project**
|
||||
```bash
|
||||
./scripts/setup/init-project.sh
|
||||
```
|
||||
|
||||
4. **Start infrastructure** (Redis, Traefik - no PostgreSQL needed)
|
||||
```bash
|
||||
cd deployments/local
|
||||
docker-compose up -d
|
||||
cd ../..
|
||||
```
|
||||
|
||||
5. **Run database migrations**
|
||||
```bash
|
||||
./scripts/db/migrate.sh auth-service dev
|
||||
```
|
||||
|
||||
6. **Seed the database**
|
||||
```bash
|
||||
./scripts/db/seed.sh auth-service
|
||||
```
|
||||
|
||||
7. **Start all services**
|
||||
```bash
|
||||
./scripts/dev/start-all.sh
|
||||
```
|
||||
|
||||
## Access Points
|
||||
|
||||
- **API Gateway**: http://localhost/api/v1
|
||||
- **Auth Service**: http://localhost:5001
|
||||
- **Web Admin**: http://admin.localhost or http://localhost:3000
|
||||
- **Web Client**: http://localhost or http://localhost:3001
|
||||
- **Traefik Dashboard**: http://localhost:8080
|
||||
|
||||
## Database
|
||||
|
||||
This project uses **Neon PostgreSQL** for all environments:
|
||||
- **Development**: Neon main branch
|
||||
- **Staging**: Neon staging branch
|
||||
- **Production**: Neon production branch
|
||||
|
||||
No local PostgreSQL needed! See [Neon Setup](../../infra/databases/neon/README.md) for details.
|
||||
|
||||
## Next Steps
|
||||
|
||||
- Read [Development Guide](development.md)
|
||||
- Check [API Documentation](../api/openapi/)
|
||||
- Review [Architecture Overview](../architecture/system-design.md)
|
||||
476
docs/en/guides/local-development.md
Normal file
476
docs/en/guides/local-development.md
Normal file
@@ -0,0 +1,476 @@
|
||||
# Local Development Guide
|
||||
|
||||
Comprehensive guide for running and developing the project locally with real-time hot reload.
|
||||
|
||||
## System Requirements
|
||||
|
||||
- **Node.js**: >= 20.0.0
|
||||
- **PNPM**: >= 8.0.0
|
||||
- **Docker & Docker Compose**: Latest version
|
||||
- **Git**: For cloning repository
|
||||
- **Neon Account**: https://neon.tech (for database)
|
||||
|
||||
## Initial Setup
|
||||
|
||||
### 1. Clone Repository
|
||||
|
||||
```bash
|
||||
git clone <repository-url>
|
||||
cd Base
|
||||
```
|
||||
|
||||
### 2. Install Dependencies
|
||||
|
||||
```bash
|
||||
pnpm install
|
||||
```
|
||||
|
||||
### 3. Setup Database (Neon)
|
||||
|
||||
Create environment configuration file:
|
||||
|
||||
```bash
|
||||
cp deployments/local/env.local.example deployments/local/.env.local
|
||||
```
|
||||
|
||||
Edit `.env.local` file and add your Neon DATABASE_URL:
|
||||
|
||||
```bash
|
||||
# Get connection string from Neon Console: https://console.neon.tech
|
||||
DATABASE_URL=postgresql://user:password@ep-xxx.region.neon.tech/dbname?sslmode=require&pgbouncer=true
|
||||
|
||||
# JWT Secrets (can keep defaults for dev)
|
||||
JWT_SECRET=dev-jwt-secret-change-in-production
|
||||
JWT_REFRESH_SECRET=dev-refresh-secret-change-in-production
|
||||
```
|
||||
|
||||
**Note**: See [Neon Database Guide](neon-database.md) for detailed setup instructions.
|
||||
|
||||
### 4. Run Database Migrations
|
||||
|
||||
```bash
|
||||
./scripts/db/migrate.sh auth-service dev
|
||||
```
|
||||
|
||||
### 5. Seed Database (Optional)
|
||||
|
||||
```bash
|
||||
./scripts/db/seed.sh auth-service
|
||||
```
|
||||
|
||||
## Ways to Run the Project
|
||||
|
||||
### Method 1: Run All Services (Recommended)
|
||||
|
||||
Best for full-stack development or testing the entire system:
|
||||
|
||||
```bash
|
||||
./scripts/dev/start-all.sh
|
||||
```
|
||||
|
||||
This script will:
|
||||
1. Check if Docker is running
|
||||
2. Verify DATABASE_URL is configured
|
||||
3. Start infrastructure (Redis, Traefik)
|
||||
4. Start all services with hot reload
|
||||
|
||||
**Or run manually:**
|
||||
|
||||
```bash
|
||||
# Step 1: Start infrastructure
|
||||
cd deployments/local
|
||||
docker-compose up -d
|
||||
cd ../..
|
||||
|
||||
# Step 2: Start all services
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
### Method 2: Run Specific Service
|
||||
|
||||
Best when working on a single service:
|
||||
|
||||
```bash
|
||||
# Using script
|
||||
./scripts/dev/start-service.sh auth-service
|
||||
|
||||
# Or run directly
|
||||
cd services/auth-service
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
### Method 3: Run Service Groups
|
||||
|
||||
```bash
|
||||
# Run only backend services
|
||||
pnpm --filter "./services/*" dev
|
||||
|
||||
# Run only frontend apps
|
||||
pnpm --filter "./apps/*" dev
|
||||
|
||||
# Run specific service
|
||||
pnpm --filter @goodgo/auth-service dev
|
||||
```
|
||||
|
||||
### Method 4: Run With Docker Compose (Full Stack)
|
||||
|
||||
```bash
|
||||
cd deployments/local
|
||||
docker-compose up -d
|
||||
|
||||
# View logs
|
||||
docker-compose logs -f
|
||||
|
||||
# Stop services
|
||||
docker-compose down
|
||||
```
|
||||
|
||||
## Access Points
|
||||
|
||||
When services are running, you can access:
|
||||
|
||||
| Service | URL | Description |
|
||||
|---------|-----|-------------|
|
||||
| **API Gateway** | http://localhost/api/v1 | Main entry point via Traefik |
|
||||
| **Auth Service** | http://localhost:5001 | Direct auth service access |
|
||||
| **Auth API** | http://localhost/api/v1/auth | Auth API via gateway |
|
||||
| **Web Admin** | http://admin.localhost or http://localhost:3000 | Admin dashboard |
|
||||
| **Web Client** | http://localhost or http://localhost:3001 | Client web app |
|
||||
| **Traefik Dashboard** | http://localhost:8080 | View routing and services |
|
||||
|
||||
## Hot Reload & Live Development
|
||||
|
||||
### Backend Services (TypeScript)
|
||||
|
||||
Backend services use `tsx watch` or `nodemon` for automatic restart on code changes:
|
||||
|
||||
```bash
|
||||
# In services/auth-service/package.json
|
||||
"scripts": {
|
||||
"dev": "tsx watch src/index.ts"
|
||||
}
|
||||
```
|
||||
|
||||
**When you change:**
|
||||
- `.ts` files → Service auto-restarts (1-2 seconds)
|
||||
- `.env` files → Manual restart required
|
||||
- `prisma/schema.prisma` → Need to run migration
|
||||
|
||||
### Frontend Apps (Next.js)
|
||||
|
||||
Frontend apps use Next.js Fast Refresh:
|
||||
|
||||
```bash
|
||||
# In apps/web-admin/package.json
|
||||
"scripts": {
|
||||
"dev": "next dev"
|
||||
}
|
||||
```
|
||||
|
||||
**When you change:**
|
||||
- React components → Updates instantly (no page reload)
|
||||
- CSS/Tailwind → Updates instantly
|
||||
- `next.config.js` → Restart required
|
||||
|
||||
### Shared Packages
|
||||
|
||||
When changing shared packages (in `packages/`):
|
||||
|
||||
```bash
|
||||
# Packages auto-rebuild with Turbo watch mode
|
||||
pnpm --filter @goodgo/logger dev
|
||||
```
|
||||
|
||||
## Real-World Development Workflow
|
||||
|
||||
### Setup 3 Terminals
|
||||
|
||||
**Terminal 1: Run Services**
|
||||
```bash
|
||||
./scripts/dev/start-all.sh
|
||||
# Or: pnpm dev
|
||||
```
|
||||
|
||||
**Terminal 2: View Logs**
|
||||
```bash
|
||||
# View specific service logs
|
||||
./scripts/dev/logs.sh auth-service
|
||||
|
||||
# Or view Docker logs
|
||||
docker logs -f redis-cache-local
|
||||
docker logs -f traefik-local
|
||||
```
|
||||
|
||||
**Terminal 3: Development Tasks**
|
||||
```bash
|
||||
# Run tests
|
||||
pnpm --filter @goodgo/auth-service test --watch
|
||||
|
||||
# Run migrations
|
||||
./scripts/db/migrate.sh auth-service dev
|
||||
|
||||
# Format code
|
||||
pnpm format
|
||||
```
|
||||
|
||||
## Health Checks
|
||||
|
||||
### Health Endpoints
|
||||
|
||||
```bash
|
||||
# Check API Gateway
|
||||
curl http://localhost/api/v1/health
|
||||
|
||||
# Check Auth Service directly
|
||||
curl http://localhost:5001/health
|
||||
|
||||
# Check Redis
|
||||
docker exec redis-cache-local redis-cli ping
|
||||
```
|
||||
|
||||
### Traefik Dashboard
|
||||
|
||||
Access http://localhost:8080 to view:
|
||||
- All active routes
|
||||
- Registered services
|
||||
- Service health status
|
||||
|
||||
## Database Development
|
||||
|
||||
### Schema Changes
|
||||
|
||||
```bash
|
||||
# 1. Edit prisma/schema.prisma
|
||||
# 2. Create and apply migration
|
||||
cd services/auth-service
|
||||
pnpm prisma migrate dev --name add_new_field
|
||||
|
||||
# 3. Prisma Client auto-regenerates
|
||||
```
|
||||
|
||||
### Reset Database (Development Only!)
|
||||
|
||||
```bash
|
||||
cd services/auth-service
|
||||
pnpm prisma migrate reset
|
||||
```
|
||||
|
||||
### View Database
|
||||
|
||||
```bash
|
||||
# Open Prisma Studio
|
||||
cd services/auth-service
|
||||
pnpm prisma studio
|
||||
# Access: http://localhost:5555
|
||||
```
|
||||
|
||||
## Debugging
|
||||
|
||||
### VS Code Debugging
|
||||
|
||||
Create `.vscode/launch.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Debug Auth Service",
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"runtimeExecutable": "pnpm",
|
||||
"runtimeArgs": ["--filter", "@goodgo/auth-service", "dev"],
|
||||
"skipFiles": ["<node_internals>/**"],
|
||||
"console": "integratedTerminal"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### View Detailed Logs
|
||||
|
||||
```bash
|
||||
# Service logs
|
||||
./scripts/dev/logs.sh auth-service
|
||||
|
||||
# Docker logs
|
||||
docker logs -f auth-service-local
|
||||
docker logs -f redis-cache-local
|
||||
docker logs -f traefik-local
|
||||
|
||||
# All logs
|
||||
docker-compose -f deployments/local/docker-compose.yml logs -f
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Port Already in Use
|
||||
|
||||
```bash
|
||||
# Find process using port
|
||||
lsof -i :5001 # Auth service
|
||||
lsof -i :3000 # Web admin
|
||||
lsof -i :6379 # Redis
|
||||
lsof -i :80 # Traefik
|
||||
|
||||
# Kill process
|
||||
kill -9 <PID>
|
||||
```
|
||||
|
||||
### Docker Not Running
|
||||
|
||||
```bash
|
||||
# Check Docker
|
||||
docker info
|
||||
|
||||
# Start Docker Desktop (macOS)
|
||||
open -a Docker
|
||||
|
||||
# Restart Docker services
|
||||
docker-compose -f deployments/local/docker-compose.yml restart
|
||||
```
|
||||
|
||||
### Database Connection Failed
|
||||
|
||||
```bash
|
||||
# Check DATABASE_URL
|
||||
cat deployments/local/.env.local | grep DATABASE_URL
|
||||
|
||||
# Test connection
|
||||
cd services/auth-service
|
||||
pnpm prisma db pull
|
||||
```
|
||||
|
||||
### Module Not Found
|
||||
|
||||
```bash
|
||||
# Cleanup and reinstall
|
||||
./scripts/utils/cleanup.sh
|
||||
pnpm install
|
||||
|
||||
# Or just cleanup node_modules
|
||||
rm -rf node_modules
|
||||
rm -rf services/*/node_modules
|
||||
rm -rf apps/*/node_modules
|
||||
rm -rf packages/*/node_modules
|
||||
pnpm install
|
||||
```
|
||||
|
||||
### Hot Reload Not Working
|
||||
|
||||
```bash
|
||||
# Restart service
|
||||
# Press Ctrl+C to stop, then:
|
||||
pnpm dev
|
||||
|
||||
# Or restart Docker container
|
||||
docker-compose -f deployments/local/docker-compose.yml restart auth-service
|
||||
```
|
||||
|
||||
## Tips & Best Practices
|
||||
|
||||
### 1. Use Turbo Cache
|
||||
|
||||
Turbo cache speeds up builds:
|
||||
|
||||
```bash
|
||||
# First run will be slow
|
||||
pnpm dev
|
||||
|
||||
# Subsequent runs will be faster thanks to cache
|
||||
# Cache stored in node_modules/.cache/turbo
|
||||
```
|
||||
|
||||
### 2. Dev Selective Services
|
||||
|
||||
No need to run everything if working on one service:
|
||||
|
||||
```bash
|
||||
# Run only auth-service
|
||||
pnpm --filter @goodgo/auth-service dev
|
||||
|
||||
# Run auth-service with dependencies
|
||||
pnpm --filter @goodgo/auth-service... dev
|
||||
```
|
||||
|
||||
### 3. Watch Tests
|
||||
|
||||
```bash
|
||||
# Run tests automatically on code changes
|
||||
pnpm --filter @goodgo/auth-service test --watch
|
||||
```
|
||||
|
||||
### 4. Auto-format Code
|
||||
|
||||
Install Prettier extension in VS Code and enable format on save.
|
||||
|
||||
### 5. Use Git Hooks
|
||||
|
||||
```bash
|
||||
# Pre-commit hook will auto-format and lint
|
||||
git commit -m "feat: add new feature"
|
||||
```
|
||||
|
||||
## Environment Variables
|
||||
|
||||
### Development (.env.local)
|
||||
|
||||
```bash
|
||||
# Database
|
||||
DATABASE_URL=postgresql://user:pass@ep-xxx.neon.tech/db?sslmode=require&pgbouncer=true
|
||||
|
||||
# Redis
|
||||
REDIS_HOST=redis
|
||||
REDIS_PORT=6379
|
||||
|
||||
# JWT
|
||||
JWT_SECRET=dev-jwt-secret
|
||||
JWT_REFRESH_SECRET=dev-refresh-secret
|
||||
|
||||
# Service
|
||||
NODE_ENV=development
|
||||
LOG_LEVEL=debug
|
||||
```
|
||||
|
||||
### Override for Specific Service
|
||||
|
||||
Create `.env.local` file in service directory:
|
||||
|
||||
```bash
|
||||
# services/auth-service/.env.local
|
||||
PORT=5001
|
||||
LOG_LEVEL=debug
|
||||
```
|
||||
|
||||
## Useful Commands
|
||||
|
||||
```bash
|
||||
# Development
|
||||
pnpm dev # Run all services
|
||||
pnpm build # Build all
|
||||
pnpm test # Test all
|
||||
pnpm lint # Lint all
|
||||
pnpm format # Format code
|
||||
|
||||
# Cleanup
|
||||
pnpm clean # Remove build artifacts
|
||||
./scripts/utils/cleanup.sh # Full cleanup
|
||||
|
||||
# Database
|
||||
./scripts/db/migrate.sh auth-service dev # Migration
|
||||
./scripts/db/seed.sh auth-service # Seed data
|
||||
./scripts/db/backup.sh auth-service # Backup
|
||||
|
||||
# Docker
|
||||
docker-compose -f deployments/local/docker-compose.yml up -d # Start
|
||||
docker-compose -f deployments/local/docker-compose.yml down # Stop
|
||||
docker-compose -f deployments/local/docker-compose.yml logs -f # Logs
|
||||
docker-compose -f deployments/local/docker-compose.yml restart # Restart
|
||||
```
|
||||
|
||||
## Additional Resources
|
||||
|
||||
- [Getting Started](getting-started.md) - Initial setup
|
||||
- [Development Guide](development.md) - Development workflow
|
||||
- [Neon Database Guide](neon-database.md) - Database guide
|
||||
- [Troubleshooting](troubleshooting.md) - Problem solving
|
||||
215
docs/en/guides/neon-database.md
Normal file
215
docs/en/guides/neon-database.md
Normal file
@@ -0,0 +1,215 @@
|
||||
# Neon Database Guide
|
||||
|
||||
This project uses [Neon PostgreSQL](https://neon.tech) for all environments.
|
||||
|
||||
## Why Neon?
|
||||
|
||||
- ✅ **Serverless**: No infrastructure management
|
||||
- ✅ **Branching**: Separate databases for dev/staging/prod
|
||||
- ✅ **Auto-scaling**: Handles traffic spikes automatically
|
||||
- ✅ **Point-in-time restore**: Easy recovery from mistakes
|
||||
- ✅ **Free tier**: Perfect for development
|
||||
- ✅ **Connection pooling**: Built-in PgBouncer support
|
||||
|
||||
## Quick Start
|
||||
|
||||
### 1. Create Neon Account
|
||||
|
||||
1. Sign up at https://neon.tech
|
||||
2. Create a new project: `goodgo-platform`
|
||||
|
||||
### 2. Create Branches
|
||||
|
||||
In Neon Console, create branches:
|
||||
- `main` (development) - already exists
|
||||
- `staging` - create from main
|
||||
- `production` - create from main
|
||||
|
||||
### 3. Get Connection Strings
|
||||
|
||||
For each branch, copy the connection string:
|
||||
- Format: `postgresql://user:password@ep-xxx.region.neon.tech/dbname?sslmode=require`
|
||||
- Add `?pgbouncer=true` for connection pooling (recommended)
|
||||
|
||||
### 4. Configure Local Development
|
||||
|
||||
```bash
|
||||
# Create .env.local
|
||||
cp deployments/local/env.local.example deployments/local/.env.local
|
||||
|
||||
# Edit .env.local and add:
|
||||
DATABASE_URL=postgresql://user:pass@ep-xxx.region.neon.tech/dbname?sslmode=require&pgbouncer=true
|
||||
```
|
||||
|
||||
### 5. Run Migrations
|
||||
|
||||
```bash
|
||||
./scripts/db/migrate.sh auth-service dev
|
||||
```
|
||||
|
||||
## Connection String Format
|
||||
|
||||
```
|
||||
postgresql://[user]:[password]@[endpoint]/[dbname]?sslmode=require&pgbouncer=true
|
||||
```
|
||||
|
||||
**Parameters**:
|
||||
- `sslmode=require` - Required for Neon
|
||||
- `pgbouncer=true` - Enable connection pooling (recommended)
|
||||
|
||||
## Environment Configuration
|
||||
|
||||
### Local Development
|
||||
|
||||
File: `deployments/local/.env.local`
|
||||
|
||||
```bash
|
||||
DATABASE_URL=postgresql://user:pass@ep-xxx.region.neon.tech/dbname?sslmode=require&pgbouncer=true
|
||||
```
|
||||
|
||||
### Staging
|
||||
|
||||
Store in GitHub Secrets: `NEON_DATABASE_URL_STAGING`
|
||||
|
||||
Or in Kubernetes:
|
||||
```bash
|
||||
kubectl create secret generic auth-service-secrets \
|
||||
--from-literal=database-url='postgresql://...' \
|
||||
-n staging
|
||||
```
|
||||
|
||||
### Production
|
||||
|
||||
Store in GitHub Secrets: `NEON_DATABASE_URL_PRODUCTION`
|
||||
|
||||
Or in Kubernetes:
|
||||
```bash
|
||||
kubectl create secret generic auth-service-secrets \
|
||||
--from-literal=database-url='postgresql://...' \
|
||||
-n production
|
||||
```
|
||||
|
||||
## Migrations
|
||||
|
||||
### Development
|
||||
|
||||
```bash
|
||||
# Create new migration
|
||||
./scripts/db/migrate.sh auth-service dev
|
||||
|
||||
# This will:
|
||||
# 1. Create migration file
|
||||
# 2. Apply to database
|
||||
# 3. Update Prisma Client
|
||||
```
|
||||
|
||||
### Staging/Production
|
||||
|
||||
Migrations run automatically in CI/CD:
|
||||
- Before deployment to staging
|
||||
- Before deployment to production (with approval)
|
||||
|
||||
Manual migration:
|
||||
```bash
|
||||
./scripts/db/migrate.sh auth-service deploy
|
||||
```
|
||||
|
||||
## Backup & Restore
|
||||
|
||||
### Automatic Backups
|
||||
|
||||
Neon provides automatic backups. Access via Neon Console:
|
||||
- Point-in-time restore
|
||||
- Branch restore
|
||||
- Export data
|
||||
|
||||
### Manual Backup
|
||||
|
||||
```bash
|
||||
./scripts/db/backup.sh auth-service
|
||||
```
|
||||
|
||||
This creates a SQL dump file in `backups/` directory.
|
||||
|
||||
### Restore
|
||||
|
||||
```bash
|
||||
# From Neon Console (recommended)
|
||||
# Or using psql:
|
||||
psql $DATABASE_URL < backup.sql
|
||||
```
|
||||
|
||||
## Monitoring
|
||||
|
||||
Monitor your databases via Neon Console:
|
||||
- Connection metrics
|
||||
- Query performance
|
||||
- Storage usage
|
||||
- Branch status
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Connection Issues
|
||||
|
||||
1. **Check connection string format**
|
||||
- Must include `?sslmode=require`
|
||||
- Verify credentials
|
||||
|
||||
2. **Check IP allowlist**
|
||||
- Neon may restrict IPs
|
||||
- Add your IP in Neon Console
|
||||
|
||||
3. **Check branch status**
|
||||
- Ensure branch is active
|
||||
- Check for maintenance
|
||||
|
||||
### Migration Issues
|
||||
|
||||
1. **DATABASE_URL not set**
|
||||
```bash
|
||||
export DATABASE_URL="your-neon-url"
|
||||
```
|
||||
|
||||
2. **Schema mismatch**
|
||||
```bash
|
||||
# Reset and re-migrate (dev only!)
|
||||
pnpm prisma migrate reset
|
||||
```
|
||||
|
||||
3. **Connection timeout**
|
||||
- Add `?pgbouncer=true` for pooling
|
||||
- Check Neon console for limits
|
||||
|
||||
### Performance Issues
|
||||
|
||||
1. **Enable connection pooling**
|
||||
- Add `?pgbouncer=true` to connection string
|
||||
|
||||
2. **Check query performance**
|
||||
- Use Neon Console query analyzer
|
||||
- Review slow queries
|
||||
|
||||
3. **Optimize indexes**
|
||||
- Review Prisma schema
|
||||
- Add indexes for frequent queries
|
||||
|
||||
## Cost Optimization
|
||||
|
||||
- **Free tier**: 0.5 GB storage, sufficient for dev
|
||||
- **Staging**: Use free tier or minimal paid plan
|
||||
- **Production**: Scale based on usage
|
||||
- **Branching**: Free branches for testing
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Always use connection pooling**: `?pgbouncer=true`
|
||||
2. **Use SSL**: `?sslmode=require`
|
||||
3. **Separate branches**: One per environment
|
||||
4. **Regular backups**: Use Neon's automatic backups
|
||||
5. **Monitor usage**: Check Neon Console regularly
|
||||
|
||||
## Resources
|
||||
|
||||
- [Neon Documentation](https://neon.tech/docs)
|
||||
- [Neon Console](https://console.neon.tech)
|
||||
- [Prisma + Neon Guide](https://neon.tech/docs/guides/prisma)
|
||||
57
docs/en/guides/troubleshooting.md
Normal file
57
docs/en/guides/troubleshooting.md
Normal file
@@ -0,0 +1,57 @@
|
||||
# Troubleshooting Guide
|
||||
|
||||
## Common Issues
|
||||
|
||||
### Database Connection Failed
|
||||
|
||||
**Symptoms**: Service can't connect to database
|
||||
|
||||
**Solutions**:
|
||||
1. Check if PostgreSQL is running: `docker ps`
|
||||
2. Verify DATABASE_URL in .env
|
||||
3. Check network connectivity: `docker network ls`
|
||||
4. Review logs: `docker logs postgres-auth-local`
|
||||
|
||||
### Port Already in Use
|
||||
|
||||
**Symptoms**: Service fails to start with port error
|
||||
|
||||
**Solutions**:
|
||||
1. Find process using port: `lsof -i :5001`
|
||||
2. Kill process or change PORT in .env
|
||||
3. Check docker-compose for port conflicts
|
||||
|
||||
### Prisma Client Not Generated
|
||||
|
||||
**Symptoms**: Import errors for Prisma Client
|
||||
|
||||
**Solutions**:
|
||||
```bash
|
||||
cd services/auth-service
|
||||
pnpm prisma generate
|
||||
```
|
||||
|
||||
### Build Failures
|
||||
|
||||
**Symptoms**: TypeScript or build errors
|
||||
|
||||
**Solutions**:
|
||||
1. Clean build artifacts: `./scripts/utils/cleanup.sh`
|
||||
2. Reinstall dependencies: `pnpm install`
|
||||
3. Check TypeScript errors: `pnpm typecheck`
|
||||
|
||||
### Traefik Not Routing
|
||||
|
||||
**Symptoms**: 404 errors from Traefik
|
||||
|
||||
**Solutions**:
|
||||
1. Check Traefik dashboard: http://localhost:8080
|
||||
2. Verify service labels in docker-compose
|
||||
3. Check routes.yml configuration
|
||||
4. Review Traefik logs: `docker logs traefik-local`
|
||||
|
||||
## Getting Help
|
||||
|
||||
1. Check service logs: `./scripts/dev/logs.sh <service>`
|
||||
2. Review GitHub Issues
|
||||
3. Contact team lead
|
||||
89
docs/en/onboarding/new-developer-guide.md
Normal file
89
docs/en/onboarding/new-developer-guide.md
Normal file
@@ -0,0 +1,89 @@
|
||||
# New Developer Guide
|
||||
|
||||
Welcome to the GoodGo Microservices Platform team!
|
||||
|
||||
## First Day Checklist
|
||||
|
||||
- [ ] Access to GitHub repository
|
||||
- [ ] Access to development environment
|
||||
- [ ] Docker installed and running
|
||||
- [ ] Node.js and PNPM installed
|
||||
- [ ] IDE configured (VS Code recommended)
|
||||
- [ ] Read this guide
|
||||
|
||||
## Setup Your Development Environment
|
||||
|
||||
1. **Clone the repository**
|
||||
```bash
|
||||
git clone <repository-url>
|
||||
cd Base
|
||||
```
|
||||
|
||||
2. **Run initialization script**
|
||||
```bash
|
||||
./scripts/setup/init-project.sh
|
||||
```
|
||||
|
||||
3. **Start local infrastructure**
|
||||
```bash
|
||||
cd deployments/local
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
4. **Verify setup**
|
||||
- Check Traefik: http://localhost:8080
|
||||
- Check API: http://localhost/api/v1/health
|
||||
|
||||
## Development Tools
|
||||
|
||||
### Recommended VS Code Extensions
|
||||
|
||||
- ESLint
|
||||
- Prettier
|
||||
- Prisma
|
||||
- Docker
|
||||
- GitLens
|
||||
|
||||
### Useful Commands
|
||||
|
||||
```bash
|
||||
# Start all services
|
||||
./scripts/dev/start-all.sh
|
||||
|
||||
# Start specific service
|
||||
./scripts/dev/start-service.sh auth-service
|
||||
|
||||
# View logs
|
||||
./scripts/dev/logs.sh auth-service
|
||||
|
||||
# Run migrations
|
||||
./scripts/db/migrate.sh auth-service dev
|
||||
|
||||
# Run tests
|
||||
pnpm test
|
||||
```
|
||||
|
||||
## Code Standards
|
||||
|
||||
- **TypeScript**: Strict mode enabled
|
||||
- **Linting**: ESLint with shared config
|
||||
- **Formatting**: Prettier
|
||||
- **Commits**: Conventional Commits format
|
||||
- **Tests**: Minimum 80% coverage
|
||||
|
||||
## Getting Help
|
||||
|
||||
- Check [Documentation](../guides/)
|
||||
- Ask in team Slack channel
|
||||
- Review existing code examples
|
||||
- Pair with senior developer
|
||||
|
||||
## Next Steps
|
||||
|
||||
1. Pick a small task from backlog
|
||||
2. Create feature branch
|
||||
3. Implement and test
|
||||
4. Create pull request
|
||||
5. Get code review
|
||||
|
||||
Good luck! 🚀
|
||||
65
docs/en/runbooks/incident-response.md
Normal file
65
docs/en/runbooks/incident-response.md
Normal file
@@ -0,0 +1,65 @@
|
||||
# Incident Response Runbook
|
||||
|
||||
## Severity Levels
|
||||
|
||||
- **P0 - Critical**: Service completely down, data loss
|
||||
- **P1 - High**: Major functionality broken, affecting many users
|
||||
- **P2 - Medium**: Minor functionality broken, workaround available
|
||||
- **P3 - Low**: Cosmetic issues, no user impact
|
||||
|
||||
## Response Process
|
||||
|
||||
### 1. Acknowledge Incident
|
||||
|
||||
- Identify severity level
|
||||
- Notify team via Slack/email
|
||||
- Create incident ticket
|
||||
|
||||
### 2. Investigate
|
||||
|
||||
- Check service health endpoints
|
||||
- Review logs: `./scripts/dev/logs.sh <service>`
|
||||
- Check monitoring dashboards (Grafana)
|
||||
- Review recent deployments
|
||||
|
||||
### 3. Mitigate
|
||||
|
||||
- Apply quick fixes if available
|
||||
- Rollback if recent deployment caused issue
|
||||
- Scale up if resource constraint
|
||||
|
||||
### 4. Resolve
|
||||
|
||||
- Implement permanent fix
|
||||
- Verify resolution
|
||||
- Update documentation
|
||||
|
||||
### 5. Post-Mortem
|
||||
|
||||
- Document incident
|
||||
- Identify root cause
|
||||
- Create action items
|
||||
- Update runbooks
|
||||
|
||||
## Common Scenarios
|
||||
|
||||
### Service Down
|
||||
|
||||
1. Check Kubernetes pods: `kubectl get pods -n <namespace>`
|
||||
2. Check pod logs: `kubectl logs <pod-name> -n <namespace>`
|
||||
3. Restart service: `kubectl rollout restart deployment/<service> -n <namespace>`
|
||||
4. If persistent, rollback: `kubectl rollout undo deployment/<service> -n <namespace>`
|
||||
|
||||
### Database Issues
|
||||
|
||||
1. Check database connectivity
|
||||
2. Review slow queries
|
||||
3. Check connection pool
|
||||
4. Scale database if needed
|
||||
|
||||
### High Error Rate
|
||||
|
||||
1. Check error logs
|
||||
2. Review recent changes
|
||||
3. Check external dependencies
|
||||
4. Implement circuit breaker if needed
|
||||
71
docs/en/runbooks/rollback-procedure.md
Normal file
71
docs/en/runbooks/rollback-procedure.md
Normal file
@@ -0,0 +1,71 @@
|
||||
# Rollback Procedure
|
||||
|
||||
## When to Rollback
|
||||
|
||||
- Service is down or unstable
|
||||
- Critical bugs introduced
|
||||
- Performance degradation
|
||||
- Data corruption risk
|
||||
|
||||
## Rollback Steps
|
||||
|
||||
### Kubernetes Rollback
|
||||
|
||||
1. **Identify current version**
|
||||
```bash
|
||||
kubectl get deployment auth-service -n production -o jsonpath='{.spec.template.spec.containers[0].image}'
|
||||
```
|
||||
|
||||
2. **Rollback to previous version**
|
||||
```bash
|
||||
kubectl rollout undo deployment/auth-service -n production
|
||||
```
|
||||
|
||||
3. **Verify rollback**
|
||||
```bash
|
||||
kubectl rollout status deployment/auth-service -n production
|
||||
```
|
||||
|
||||
4. **Check service health**
|
||||
```bash
|
||||
curl https://api.goodgo.vn/health
|
||||
```
|
||||
|
||||
### Database Migration Rollback
|
||||
|
||||
**Note**: Prisma doesn't support automatic rollback. Create a new migration to reverse changes.
|
||||
|
||||
1. Create reverse migration:
|
||||
```bash
|
||||
cd services/auth-service
|
||||
pnpm prisma migrate dev --name rollback_previous_change
|
||||
```
|
||||
|
||||
2. Apply reverse migration:
|
||||
```bash
|
||||
pnpm prisma migrate deploy
|
||||
```
|
||||
|
||||
### Docker Compose Rollback
|
||||
|
||||
1. Stop current containers:
|
||||
```bash
|
||||
docker-compose down
|
||||
```
|
||||
|
||||
2. Checkout previous version:
|
||||
```bash
|
||||
git checkout <previous-commit>
|
||||
```
|
||||
|
||||
3. Rebuild and start:
|
||||
```bash
|
||||
docker-compose up -d --build
|
||||
```
|
||||
|
||||
## Post-Rollback
|
||||
|
||||
1. Verify functionality
|
||||
2. Monitor metrics
|
||||
3. Document rollback reason
|
||||
4. Plan fix for next deployment
|
||||
102
docs/vi/api/openapi/auth-service.yaml
Normal file
102
docs/vi/api/openapi/auth-service.yaml
Normal file
@@ -0,0 +1,102 @@
|
||||
openapi: 3.0.0
|
||||
info:
|
||||
title: Auth Service API
|
||||
version: 1.0.0
|
||||
description: Authentication and Authorization Service API
|
||||
|
||||
servers:
|
||||
- url: http://localhost/api/v1
|
||||
description: Local development
|
||||
- url: https://api.goodgo.vn/api/v1
|
||||
description: Production
|
||||
|
||||
paths:
|
||||
/auth/register:
|
||||
post:
|
||||
summary: Register new user
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
email:
|
||||
type: string
|
||||
format: email
|
||||
password:
|
||||
type: string
|
||||
minLength: 6
|
||||
confirmPassword:
|
||||
type: string
|
||||
responses:
|
||||
'201':
|
||||
description: User registered successfully
|
||||
'400':
|
||||
description: Validation error
|
||||
|
||||
/auth/login:
|
||||
post:
|
||||
summary: Login user
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
email:
|
||||
type: string
|
||||
format: email
|
||||
password:
|
||||
type: string
|
||||
responses:
|
||||
'200':
|
||||
description: Login successful
|
||||
'401':
|
||||
description: Invalid credentials
|
||||
|
||||
/auth/logout:
|
||||
post:
|
||||
summary: Logout user
|
||||
security:
|
||||
- bearerAuth: []
|
||||
responses:
|
||||
'200':
|
||||
description: Logout successful
|
||||
|
||||
/auth/refresh:
|
||||
post:
|
||||
summary: Refresh access token
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
refreshToken:
|
||||
type: string
|
||||
responses:
|
||||
'200':
|
||||
description: Token refreshed
|
||||
'401':
|
||||
description: Invalid refresh token
|
||||
|
||||
/users/me:
|
||||
get:
|
||||
summary: Get current user
|
||||
security:
|
||||
- bearerAuth: []
|
||||
responses:
|
||||
'200':
|
||||
description: User information
|
||||
'401':
|
||||
description: Unauthorized
|
||||
|
||||
components:
|
||||
securitySchemes:
|
||||
bearerAuth:
|
||||
type: http
|
||||
scheme: bearer
|
||||
bearerFormat: JWT
|
||||
58
docs/vi/architecture/service-communication.md
Normal file
58
docs/vi/architecture/service-communication.md
Normal file
@@ -0,0 +1,58 @@
|
||||
# Giao Tiếp Giữa Các Service
|
||||
|
||||
## Các Mẫu Giao Tiếp
|
||||
|
||||
### Giao Tiếp Đồng Bộ (HTTP/REST)
|
||||
|
||||
Các service giao tiếp đồng bộ qua HTTP REST APIs thông qua Traefik API Gateway.
|
||||
|
||||
**Ví dụ:**
|
||||
```typescript
|
||||
// Web App -> Auth Service
|
||||
const response = await fetch('http://api.goodgo.vn/api/v1/auth/login', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ email, password }),
|
||||
});
|
||||
```
|
||||
|
||||
### Giao Tiếp Service-to-Service
|
||||
|
||||
Các service có thể giao tiếp trực tiếp qua mạng nội bộ:
|
||||
|
||||
```typescript
|
||||
// Auth Service -> Notification Service (tương lai)
|
||||
const response = await fetch('http://notification-service:5003/api/v1/notifications', {
|
||||
method: 'POST',
|
||||
headers: { 'X-Service-Auth': process.env.INTERNAL_API_KEY },
|
||||
body: JSON.stringify({ userId, message }),
|
||||
});
|
||||
```
|
||||
|
||||
## API Gateway Routing
|
||||
|
||||
Traefik định tuyến requests dựa trên:
|
||||
- Host header (`api.goodgo.vn`)
|
||||
- Path prefix (`/api/v1/auth`)
|
||||
|
||||
## Xử Lý Lỗi
|
||||
|
||||
Tất cả services tuân theo định dạng error response nhất quán:
|
||||
|
||||
```json
|
||||
{
|
||||
"success": false,
|
||||
"error": {
|
||||
"code": "AUTH_001",
|
||||
"message": "Invalid credentials",
|
||||
"details": {}
|
||||
},
|
||||
"timestamp": "2024-01-01T00:00:00.000Z"
|
||||
}
|
||||
```
|
||||
|
||||
## Retry và Circuit Breaker
|
||||
|
||||
Triển khai trong tương lai:
|
||||
- Exponential backoff cho retries
|
||||
- Circuit breaker pattern cho fault tolerance
|
||||
- Fallback mechanisms
|
||||
81
docs/vi/architecture/system-design.md
Normal file
81
docs/vi/architecture/system-design.md
Normal file
@@ -0,0 +1,81 @@
|
||||
# Thiết Kế Hệ Thống
|
||||
|
||||
## Tổng Quan
|
||||
|
||||
GoodGo Microservices Platform được xây dựng sử dụng kiến trúc microservices với các nguyên tắc sau:
|
||||
|
||||
- **Độc Lập Service**: Mỗi service có database riêng và có thể deploy độc lập
|
||||
- **API Gateway**: Traefik xử lý routing, load balancing, và các concerns xuyên suốt
|
||||
- **Shared Libraries**: Chức năng chung được trích xuất vào shared packages
|
||||
- **Infrastructure as Code**: Tất cả cấu hình infrastructure đều được version
|
||||
- **Observability**: Đầy đủ khả năng monitoring, logging, và tracing
|
||||
|
||||
## Sơ Đồ Kiến Trúc
|
||||
|
||||
```
|
||||
┌─────────────┐ ┌─────────────┐
|
||||
│ Web App │ │ Mobile App │
|
||||
│ (Next.js) │ │ (React Native)
|
||||
└──────┬──────┘ └──────┬──────┘
|
||||
│ │
|
||||
└──────────┬────────┘
|
||||
│
|
||||
┌────────▼────────┐
|
||||
│ Traefik │
|
||||
│ (API Gateway) │
|
||||
└────────┬─────────┘
|
||||
│
|
||||
┌─────────────┼─────────────┐
|
||||
│ │ │
|
||||
┌───▼────┐ ┌───▼────┐ ┌───▼────┐
|
||||
│ Auth │ │ Future │ │ Future │
|
||||
│Service │ │Service │ │Service │
|
||||
└───┬────┘ └───┬────┘ └───┬────┘
|
||||
│ │ │
|
||||
└────────────┼────────────┘
|
||||
│
|
||||
┌────────────┼────────────┐
|
||||
│ │ │
|
||||
┌───▼────┐ ┌───▼────┐ ┌───▼────┐
|
||||
│Postgres│ │ Redis │ │Prometheus│
|
||||
└────────┘ └────────┘ └─────────┘
|
||||
```
|
||||
|
||||
## Các Thành Phần
|
||||
|
||||
### Frontend Layer
|
||||
- **Web App**: Ứng dụng Next.js với App Router
|
||||
- **Mobile App**: Ứng dụng React Native
|
||||
|
||||
### API Gateway
|
||||
- **Traefik**: Reverse proxy, load balancer, SSL termination
|
||||
|
||||
### Services Layer
|
||||
- **Auth Service**: Xác thực và phân quyền
|
||||
- **Future Services**: Payment, Order, Notification, v.v.
|
||||
|
||||
### Infrastructure Layer
|
||||
- **PostgreSQL**: Database chính
|
||||
- **Redis**: Caching và session storage
|
||||
- **Prometheus**: Thu thập metrics
|
||||
- **Grafana**: Hiển thị metrics
|
||||
- **Loki**: Tập hợp logs
|
||||
|
||||
## Các Mẫu Giao Tiếp
|
||||
|
||||
- **Đồng Bộ**: HTTP/REST cho các mẫu request-response
|
||||
- **Bất Đồng Bộ**: Message queues (triển khai trong tương lai)
|
||||
- **Service Discovery**: Docker networking và Kubernetes DNS
|
||||
|
||||
## Quản Lý Dữ Liệu
|
||||
|
||||
- **Database per Service**: Mỗi service sở hữu dữ liệu của mình
|
||||
- **API Composition**: Services expose APIs để truy cập dữ liệu
|
||||
- **Event Sourcing**: Xem xét trong tương lai cho audit trails
|
||||
|
||||
## Bảo Mật
|
||||
|
||||
- **Authentication**: JWT tokens với refresh token rotation
|
||||
- **Authorization**: Role-based access control (RBAC)
|
||||
- **Network Security**: TLS/SSL, rate limiting, CORS
|
||||
- **Secrets Management**: Environment variables, Kubernetes secrets
|
||||
106
docs/vi/guides/deployment.md
Normal file
106
docs/vi/guides/deployment.md
Normal file
@@ -0,0 +1,106 @@
|
||||
# Hướng Dẫn Deployment
|
||||
|
||||
## Thiết Lập Database (Neon)
|
||||
|
||||
Tất cả môi trường sử dụng **Neon PostgreSQL**. Thiết lập một lần trước khi deploy:
|
||||
|
||||
1. Tạo Neon project tại https://neon.tech
|
||||
2. Tạo branches: `main` (dev), `staging`, `production`
|
||||
3. Lấy connection strings cho mỗi branch
|
||||
4. Cấu hình trong environment variables (xem bên dưới)
|
||||
|
||||
Xem [Hướng Dẫn Thiết Lập Neon](../../infra/databases/neon/README.md) để biết chi tiết.
|
||||
|
||||
## Local Deployment
|
||||
|
||||
```bash
|
||||
# Setup Neon database URL
|
||||
cp deployments/local/env.local.example deployments/local/.env.local
|
||||
# Chỉnh sửa .env.local và thêm Neon DATABASE_URL của bạn
|
||||
|
||||
# Khởi động services (không cần PostgreSQL container)
|
||||
cd deployments/local
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
## Staging Deployment
|
||||
|
||||
### Yêu Cầu
|
||||
- Quyền truy cập Kubernetes cluster
|
||||
- kubectl đã cấu hình
|
||||
- KUBECONFIG đã set
|
||||
- Neon staging branch đã tạo
|
||||
- GitHub Secrets đã cấu hình:
|
||||
- `NEON_DATABASE_URL_STAGING`
|
||||
- `KUBECONFIG_STAGING`
|
||||
|
||||
### Thiết Lập Secrets
|
||||
|
||||
```bash
|
||||
# Tạo Kubernetes secret
|
||||
kubectl create secret generic auth-service-secrets \
|
||||
--from-literal=database-url='postgresql://user:pass@ep-xxx.region.neon.tech/dbname?sslmode=require&pgbouncer=true' \
|
||||
--from-literal=jwt-secret='your-staging-jwt-secret' \
|
||||
--from-literal=jwt-refresh-secret='your-staging-refresh-secret' \
|
||||
-n staging
|
||||
```
|
||||
|
||||
### Deploy
|
||||
|
||||
```bash
|
||||
./scripts/deploy/deploy-staging.sh
|
||||
```
|
||||
|
||||
Hoặc thủ công:
|
||||
```bash
|
||||
kubectl apply -f deployments/staging/kubernetes/
|
||||
```
|
||||
|
||||
**Lưu ý**: Migrations chạy tự động trong CI/CD trước khi deployment.
|
||||
|
||||
## Production Deployment
|
||||
|
||||
### Yêu Cầu
|
||||
- Production Kubernetes cluster
|
||||
- kubectl đã cấu hình với production context
|
||||
- Neon production branch đã tạo
|
||||
- GitHub Secrets đã cấu hình:
|
||||
- `NEON_DATABASE_URL_PRODUCTION`
|
||||
- `KUBECONFIG_PRODUCTION`
|
||||
|
||||
### Thiết Lập Secrets
|
||||
|
||||
```bash
|
||||
# Tạo Kubernetes secret
|
||||
kubectl create secret generic auth-service-secrets \
|
||||
--from-literal=database-url='postgresql://user:pass@ep-xxx.region.neon.tech/dbname?sslmode=require&pgbouncer=true' \
|
||||
--from-literal=jwt-secret='your-production-jwt-secret' \
|
||||
--from-literal=jwt-refresh-secret='your-production-refresh-secret' \
|
||||
-n production
|
||||
```
|
||||
|
||||
### Deploy
|
||||
|
||||
```bash
|
||||
./scripts/deploy/deploy-prod.sh
|
||||
```
|
||||
|
||||
**Lưu ý**: Migrations chạy tự động trong CI/CD trước khi deployment (cần approval).
|
||||
|
||||
### Rollback
|
||||
|
||||
```bash
|
||||
kubectl rollout undo deployment/auth-service -n production
|
||||
```
|
||||
|
||||
## Health Checks
|
||||
|
||||
- Liveness: `GET /health/live`
|
||||
- Readiness: `GET /health/ready`
|
||||
- Health: `GET /health`
|
||||
|
||||
## Monitoring
|
||||
|
||||
- Prometheus: http://prometheus:9090
|
||||
- Grafana: http://grafana:3000
|
||||
- Traefik Dashboard: http://traefik:8080
|
||||
87
docs/vi/guides/development.md
Normal file
87
docs/vi/guides/development.md
Normal file
@@ -0,0 +1,87 @@
|
||||
# Hướng Dẫn Development
|
||||
|
||||
## Cấu Trúc Dự Án
|
||||
|
||||
```
|
||||
├── apps/ # Frontend applications
|
||||
├── services/ # Backend microservices
|
||||
├── packages/ # Shared libraries
|
||||
├── infra/ # Infrastructure configs
|
||||
├── deployments/ # Deployment configs
|
||||
├── scripts/ # Automation scripts
|
||||
└── docs/ # Documentation
|
||||
```
|
||||
|
||||
## Quy Trình Development
|
||||
|
||||
### 1. Tạo Feature Branch
|
||||
|
||||
```bash
|
||||
git checkout -b feature/my-feature
|
||||
```
|
||||
|
||||
### 2. Thực Hiện Thay Đổi
|
||||
|
||||
- Viết code tuân theo TypeScript strict mode
|
||||
- Thêm tests cho chức năng mới
|
||||
- Cập nhật tài liệu nếu cần
|
||||
|
||||
### 3. Chạy Tests Locally
|
||||
|
||||
```bash
|
||||
# Tất cả tests
|
||||
pnpm test
|
||||
|
||||
# Service cụ thể
|
||||
pnpm --filter @goodgo/auth-service test
|
||||
```
|
||||
|
||||
### 4. Lint và Format
|
||||
|
||||
```bash
|
||||
pnpm lint
|
||||
pnpm format
|
||||
```
|
||||
|
||||
### 5. Tạo Pull Request
|
||||
|
||||
- Push branch của bạn
|
||||
- Tạo PR target `develop`
|
||||
- CI/CD sẽ chạy tự động
|
||||
|
||||
## Thêm Service Mới
|
||||
|
||||
1. Sử dụng template:
|
||||
```bash
|
||||
./scripts/utils/create-service.sh my-new-service
|
||||
```
|
||||
|
||||
2. Cập nhật cấu hình service
|
||||
3. Implement business logic
|
||||
4. Thêm tests
|
||||
5. Cập nhật tài liệu
|
||||
|
||||
## Thêm Package Mới
|
||||
|
||||
1. Tạo package trong `packages/new-package`
|
||||
2. Thêm vào workspace trong `pnpm-workspace.yaml`
|
||||
3. Export từ `index.ts`
|
||||
4. Thêm tests
|
||||
5. Ghi lại cách sử dụng
|
||||
|
||||
## Database Migrations
|
||||
|
||||
```bash
|
||||
# Tạo migration
|
||||
cd services/auth-service
|
||||
pnpm prisma migrate dev --name add_new_field
|
||||
|
||||
# Áp dụng migrations (production)
|
||||
pnpm prisma migrate deploy
|
||||
```
|
||||
|
||||
## Debugging
|
||||
|
||||
- Sử dụng logger từ `@goodgo/logger`
|
||||
- Kiểm tra Traefik logs: `docker logs traefik-local`
|
||||
- Kiểm tra service logs: `./scripts/dev/logs.sh auth-service`
|
||||
81
docs/vi/guides/getting-started.md
Normal file
81
docs/vi/guides/getting-started.md
Normal file
@@ -0,0 +1,81 @@
|
||||
# Bắt Đầu
|
||||
|
||||
## Yêu Cầu
|
||||
|
||||
- Node.js >= 20.0.0
|
||||
- PNPM >= 8.0.0
|
||||
- Docker & Docker Compose
|
||||
- Git
|
||||
- Tài khoản Neon (https://neon.tech) - cho database
|
||||
|
||||
## Thiết Lập Ban Đầu
|
||||
|
||||
1. **Clone repository**
|
||||
```bash
|
||||
git clone <repository-url>
|
||||
cd Base
|
||||
```
|
||||
|
||||
2. **Thiết Lập Neon Database**
|
||||
```bash
|
||||
# Chạy script setup
|
||||
./scripts/db/setup-neon.sh
|
||||
|
||||
# Hoặc thủ công:
|
||||
# 1. Tạo Neon project tại https://neon.tech
|
||||
# 2. Tạo branches: main (dev), staging, production
|
||||
# 3. Lấy connection strings
|
||||
# 4. Cập nhật deployments/local/.env.local
|
||||
```
|
||||
|
||||
Xem [Hướng Dẫn Thiết Lập Neon](../../infra/databases/neon/README.md) để biết chi tiết.
|
||||
|
||||
3. **Khởi tạo project**
|
||||
```bash
|
||||
./scripts/setup/init-project.sh
|
||||
```
|
||||
|
||||
4. **Khởi động infrastructure** (Redis, Traefik - không cần PostgreSQL)
|
||||
```bash
|
||||
cd deployments/local
|
||||
docker-compose up -d
|
||||
cd ../..
|
||||
```
|
||||
|
||||
5. **Chạy database migrations**
|
||||
```bash
|
||||
./scripts/db/migrate.sh auth-service dev
|
||||
```
|
||||
|
||||
6. **Seed database**
|
||||
```bash
|
||||
./scripts/db/seed.sh auth-service
|
||||
```
|
||||
|
||||
7. **Khởi động tất cả services**
|
||||
```bash
|
||||
./scripts/dev/start-all.sh
|
||||
```
|
||||
|
||||
## Điểm Truy Cập
|
||||
|
||||
- **API Gateway**: http://localhost/api/v1
|
||||
- **Auth Service**: http://localhost:5001
|
||||
- **Web Admin**: http://admin.localhost hoặc http://localhost:3000
|
||||
- **Web Client**: http://localhost hoặc http://localhost:3001
|
||||
- **Traefik Dashboard**: http://localhost:8080
|
||||
|
||||
## Database
|
||||
|
||||
Project này sử dụng **Neon PostgreSQL** cho tất cả môi trường:
|
||||
- **Development**: Neon main branch
|
||||
- **Staging**: Neon staging branch
|
||||
- **Production**: Neon production branch
|
||||
|
||||
Không cần PostgreSQL local! Xem [Thiết Lập Neon](../../infra/databases/neon/README.md) để biết chi tiết.
|
||||
|
||||
## Các Bước Tiếp Theo
|
||||
|
||||
- Đọc [Hướng Dẫn Development](development.md)
|
||||
- Xem [Tài Liệu API](../api/openapi/)
|
||||
- Xem lại [Tổng Quan Kiến Trúc](../architecture/system-design.md)
|
||||
741
docs/vi/guides/local-development.md
Normal file
741
docs/vi/guides/local-development.md
Normal file
@@ -0,0 +1,741 @@
|
||||
# Hướng Dẫn Development Local
|
||||
|
||||
Hướng dẫn chi tiết cách chạy và phát triển dự án trên máy local với hot reload real-time.
|
||||
|
||||
## Yêu Cầu Hệ Thống
|
||||
|
||||
- **Node.js**: >= 20.0.0
|
||||
- **PNPM**: >= 8.0.0
|
||||
- **Docker & Docker Compose**: Phiên bản mới nhất
|
||||
- **Git**: Để clone repository
|
||||
- **Tài khoản Neon**: https://neon.tech (cho database)
|
||||
|
||||
## Thiết Lập Ban Đầu
|
||||
|
||||
### 1. Clone Repository
|
||||
|
||||
```bash
|
||||
git clone <repository-url>
|
||||
cd Base
|
||||
```
|
||||
|
||||
### 2. Cài Đặt Dependencies
|
||||
|
||||
```bash
|
||||
pnpm install
|
||||
```
|
||||
|
||||
### 3. Thiết Lập Environment Variables
|
||||
|
||||
Dự án sử dụng **Hybrid Environment Configuration**:
|
||||
- **Shared configs** (JWT secrets, Redis): `deployments/local/.env.local`
|
||||
- **Service-specific configs** (DATABASE_URL, PORT): `services/<service>/.env.local`
|
||||
|
||||
#### 3.1. Tạo Shared Environment File
|
||||
|
||||
```bash
|
||||
cp deployments/local/env.local.example deployments/local/.env.local
|
||||
```
|
||||
|
||||
File này chứa configs dùng chung cho tất cả services:
|
||||
|
||||
```bash
|
||||
# JWT Secrets - MUST be same across all services
|
||||
JWT_SECRET=dev-jwt-secret-change-in-production-min-32-chars
|
||||
JWT_REFRESH_SECRET=dev-refresh-secret-change-in-production-min-32-chars
|
||||
|
||||
# Redis (Docker)
|
||||
REDIS_HOST=redis
|
||||
REDIS_PORT=6379
|
||||
|
||||
# Common configs
|
||||
NODE_ENV=development
|
||||
LOG_LEVEL=debug
|
||||
CORS_ORIGIN=http://localhost:3000,http://localhost:3001
|
||||
```
|
||||
|
||||
#### 3.2. Tạo Service-Specific Environment File
|
||||
|
||||
Mỗi service cần file `.env.local` riêng cho DATABASE_URL và configs cụ thể:
|
||||
|
||||
```bash
|
||||
# Ví dụ: Auth Service
|
||||
cp services/auth-service/env.local.example services/auth-service/.env.local
|
||||
```
|
||||
|
||||
Chỉnh sửa `services/auth-service/.env.local`:
|
||||
|
||||
```bash
|
||||
# Database riêng cho auth-service
|
||||
DATABASE_URL=postgresql://user:password@ep-xxx.region.neon.tech/goodgo_auth_dev?sslmode=require&pgbouncer=true
|
||||
|
||||
# Service configs
|
||||
PORT=5001
|
||||
SERVICE_NAME=auth-service
|
||||
|
||||
# Redis override (native dev - Redis in Docker)
|
||||
REDIS_HOST=localhost
|
||||
```
|
||||
|
||||
**Lưu ý**:
|
||||
- Mỗi service có **database riêng** (microservices pattern)
|
||||
- JWT secrets **phải giống nhau** để services verify tokens của nhau
|
||||
- Xem [Hướng Dẫn Neon Database](neon-database.md) để tạo databases
|
||||
|
||||
### 4. Chạy Database Migrations
|
||||
|
||||
```bash
|
||||
./scripts/db/migrate.sh auth-service dev
|
||||
```
|
||||
|
||||
### 5. Seed Database (Tùy chọn)
|
||||
|
||||
```bash
|
||||
./scripts/db/seed.sh auth-service
|
||||
```
|
||||
|
||||
## Các Cách Chạy Dự Án
|
||||
|
||||
### Cách 1: Chạy Tất Cả Services (Khuyến nghị)
|
||||
|
||||
Cách này phù hợp khi bạn làm full-stack hoặc cần test toàn bộ hệ thống:
|
||||
|
||||
```bash
|
||||
./scripts/dev/start-all.sh
|
||||
```
|
||||
|
||||
Script này sẽ:
|
||||
1. Kiểm tra Docker đang chạy
|
||||
2. Kiểm tra DATABASE_URL đã được cấu hình
|
||||
3. Khởi động infrastructure (Redis, Traefik)
|
||||
4. Khởi động tất cả services với hot reload
|
||||
|
||||
**Hoặc chạy thủ công:**
|
||||
|
||||
```bash
|
||||
# Bước 1: Khởi động infrastructure
|
||||
cd deployments/local
|
||||
docker-compose up -d
|
||||
cd ../..
|
||||
|
||||
# Bước 2: Khởi động tất cả services
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
### Cách 2: Chạy Service Cụ Thể
|
||||
|
||||
Cách này phù hợp khi bạn chỉ làm việc với 1 service:
|
||||
|
||||
```bash
|
||||
# Sử dụng script
|
||||
./scripts/dev/start-service.sh auth-service
|
||||
|
||||
# Hoặc chạy trực tiếp
|
||||
cd services/auth-service
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
### Cách 3: Hybrid - Native + Docker (Linh hoạt nhất)
|
||||
|
||||
Cách này kết hợp tốt nhất của cả hai thế giới: Infrastructure chạy Docker, services đang dev chạy native với hot reload nhanh.
|
||||
|
||||
**Phù hợp khi:**
|
||||
- Làm việc với 1-2 services cụ thể
|
||||
- Cần services khác chạy background
|
||||
- Muốn hot reload nhanh cho service đang dev
|
||||
- Tiết kiệm tài nguyên máy
|
||||
|
||||
**Setup:**
|
||||
|
||||
```bash
|
||||
# Bước 1: Khởi động infrastructure (Redis, Traefik)
|
||||
cd deployments/local
|
||||
docker-compose up -d redis traefik
|
||||
cd ../..
|
||||
|
||||
# Bước 2: Chạy service đang dev với native (hot reload nhanh)
|
||||
pnpm --filter @goodgo/auth-service dev
|
||||
|
||||
# Bước 3: (Tùy chọn) Chạy services khác trong Docker nếu cần
|
||||
docker-compose -f deployments/local/docker-compose.yml up -d user-service payment-service
|
||||
```
|
||||
|
||||
**Ví dụ workflow thực tế:**
|
||||
|
||||
```bash
|
||||
# Scenario 1: Chỉ dev auth-service
|
||||
cd deployments/local && docker-compose up -d redis traefik && cd ../..
|
||||
pnpm --filter @goodgo/auth-service dev
|
||||
|
||||
# Scenario 2: Dev auth-service + cần web-admin để test
|
||||
cd deployments/local && docker-compose up -d redis traefik && cd ../..
|
||||
pnpm --filter @goodgo/auth-service dev &
|
||||
pnpm --filter @goodgo/web-admin dev
|
||||
|
||||
# Scenario 3: Dev frontend, backend chạy Docker
|
||||
cd deployments/local && docker-compose up -d redis traefik auth-service && cd ../..
|
||||
pnpm --filter @goodgo/web-admin dev
|
||||
```
|
||||
|
||||
**Lợi ích:**
|
||||
- ⚡ Hot reload cực nhanh (1-2s) cho service đang dev
|
||||
- 💻 Tiết kiệm RAM - chỉ chạy Docker cho services cần thiết
|
||||
- 🐛 Debug dễ dàng - attach debugger trực tiếp
|
||||
- 🎯 Linh hoạt - chọn service nào chạy native, service nào chạy Docker
|
||||
|
||||
### Cách 4: Chạy Nhóm Services
|
||||
|
||||
```bash
|
||||
# Chỉ chạy backend services
|
||||
pnpm --filter "./services/*" dev
|
||||
|
||||
# Chỉ chạy frontend apps
|
||||
pnpm --filter "./apps/*" dev
|
||||
|
||||
# Chạy service cụ thể với dependencies
|
||||
pnpm --filter @goodgo/auth-service... dev
|
||||
```
|
||||
|
||||
### Cách 5: Chạy Với Docker Compose (Full Stack)
|
||||
|
||||
```bash
|
||||
cd deployments/local
|
||||
docker-compose up -d
|
||||
|
||||
# Xem logs
|
||||
docker-compose logs -f
|
||||
|
||||
# Dừng services
|
||||
docker-compose down
|
||||
```
|
||||
|
||||
**Lưu ý:** Dockerfile hiện tại là production build, không có hot reload. Phù hợp để test môi trường giống production.
|
||||
|
||||
## So Sánh Các Phương Án
|
||||
|
||||
| Tiêu chí | Cách 1: All Native | Cách 3: Hybrid | Cách 5: Full Docker |
|
||||
|----------|-------------------|----------------|---------------------|
|
||||
| **Hot Reload** | ⚡ Cực nhanh (1-2s) | ⚡ Nhanh cho service native | ❌ Không có (production build) |
|
||||
| **RAM Usage** | 💚 Thấp (~1-2GB) | 💛 Trung bình (~2-3GB) | 🔴 Cao (~3-5GB) |
|
||||
| **Debug** | ✅ Dễ nhất | ✅ Dễ (native services) | ⚠️ Khó hơn (qua container) |
|
||||
| **Setup** | 🟢 Đơn giản | 🟡 Trung bình | 🟢 Đơn giản |
|
||||
| **Giống Production** | ⚠️ Khác biệt | 🟡 Một phần | ✅ Gần giống nhất |
|
||||
| **Khi nào dùng** | Dev hàng ngày | Dev 1-2 services | Test integration/deployment |
|
||||
|
||||
**Khuyến nghị:**
|
||||
- 🎯 **90% thời gian**: Dùng **Cách 1** (All Native) - nhanh nhất, tiện nhất
|
||||
- 🔧 **Khi cần linh hoạt**: Dùng **Cách 3** (Hybrid) - chọn service nào chạy native
|
||||
- 🐳 **Test production-like**: Dùng **Cách 5** (Full Docker) - test networking, deployment
|
||||
|
||||
## Điểm Truy Cập
|
||||
|
||||
Khi các services đang chạy, bạn có thể truy cập:
|
||||
|
||||
| Service | URL | Mô tả |
|
||||
|---------|-----|-------|
|
||||
| **API Gateway** | http://localhost/api/v1 | Điểm truy cập chính qua Traefik |
|
||||
| **Auth Service** | http://localhost:5001 | Truy cập trực tiếp auth service |
|
||||
| **Auth API** | http://localhost/api/v1/auth | Auth API qua gateway |
|
||||
| **Web Admin** | http://admin.localhost hoặc http://localhost:3000 | Admin dashboard |
|
||||
| **Web Client** | http://localhost hoặc http://localhost:3001 | Client web app |
|
||||
| **Traefik Dashboard** | http://localhost:8080 | Xem routing và services |
|
||||
|
||||
## Hot Reload & Live Development
|
||||
|
||||
### Backend Services (TypeScript)
|
||||
|
||||
Backend services sử dụng `tsx watch` hoặc `nodemon` để tự động restart khi code thay đổi:
|
||||
|
||||
```bash
|
||||
# Trong services/auth-service/package.json
|
||||
"scripts": {
|
||||
"dev": "tsx watch src/index.ts"
|
||||
}
|
||||
```
|
||||
|
||||
**Khi bạn thay đổi:**
|
||||
- `.ts` files → Service tự động restart (1-2 giây)
|
||||
- `.env` files → Cần restart thủ công
|
||||
- `prisma/schema.prisma` → Cần chạy migration
|
||||
|
||||
### Frontend Apps (Next.js)
|
||||
|
||||
Frontend apps sử dụng Next.js Fast Refresh:
|
||||
|
||||
```bash
|
||||
# Trong apps/web-admin/package.json
|
||||
"scripts": {
|
||||
"dev": "next dev"
|
||||
}
|
||||
```
|
||||
|
||||
**Khi bạn thay đổi:**
|
||||
- React components → Cập nhật ngay lập tức (không reload page)
|
||||
- CSS/Tailwind → Cập nhật ngay lập tức
|
||||
- `next.config.js` → Cần restart
|
||||
|
||||
### Shared Packages
|
||||
|
||||
Khi thay đổi shared packages (trong `packages/`):
|
||||
|
||||
```bash
|
||||
# Packages tự động rebuild với Turbo watch mode
|
||||
pnpm --filter @goodgo/logger dev
|
||||
```
|
||||
|
||||
## Workflow Development Thực Tế
|
||||
|
||||
### Setup 3 Terminals (Khuyến nghị)
|
||||
|
||||
#### Option A: Development Hàng Ngày (All Native)
|
||||
|
||||
**Terminal 1: Chạy Services**
|
||||
```bash
|
||||
./scripts/dev/start-all.sh
|
||||
# Hoặc: pnpm dev
|
||||
```
|
||||
|
||||
**Terminal 2: Watch Tests**
|
||||
```bash
|
||||
# Auto-run tests khi code thay đổi
|
||||
pnpm --filter @goodgo/auth-service test --watch
|
||||
```
|
||||
|
||||
**Terminal 3: Development Tasks**
|
||||
```bash
|
||||
# Prisma Studio
|
||||
pnpm --filter @goodgo/auth-service prisma studio
|
||||
|
||||
# Xem logs
|
||||
./scripts/dev/logs.sh auth-service
|
||||
|
||||
# Migrations
|
||||
./scripts/db/migrate.sh auth-service dev
|
||||
```
|
||||
|
||||
#### Option B: Hybrid Development (Selective Services)
|
||||
|
||||
**Terminal 1: Infrastructure + Service đang dev**
|
||||
```bash
|
||||
# Start infrastructure
|
||||
cd deployments/local && docker-compose up -d redis traefik && cd ../..
|
||||
|
||||
# Dev service cụ thể với hot reload
|
||||
pnpm --filter @goodgo/auth-service dev
|
||||
```
|
||||
|
||||
**Terminal 2: Frontend (nếu cần)**
|
||||
```bash
|
||||
pnpm --filter @goodgo/web-admin dev
|
||||
```
|
||||
|
||||
**Terminal 3: Tools & Logs**
|
||||
```bash
|
||||
# Watch tests
|
||||
pnpm --filter @goodgo/auth-service test --watch
|
||||
|
||||
# Xem Docker logs
|
||||
docker logs -f redis-cache-local
|
||||
|
||||
# Quick commands
|
||||
pnpm format
|
||||
```
|
||||
|
||||
### Workflow Theo Use Case
|
||||
|
||||
#### Use Case 1: Dev Backend Service
|
||||
|
||||
```bash
|
||||
# Terminal 1
|
||||
cd deployments/local && docker-compose up -d redis traefik && cd ../..
|
||||
pnpm --filter @goodgo/auth-service dev
|
||||
|
||||
# Terminal 2
|
||||
pnpm --filter @goodgo/auth-service test --watch
|
||||
|
||||
# Terminal 3
|
||||
pnpm --filter @goodgo/auth-service prisma studio
|
||||
```
|
||||
|
||||
#### Use Case 2: Dev Frontend + Backend
|
||||
|
||||
```bash
|
||||
# Terminal 1: Backend
|
||||
pnpm --filter @goodgo/auth-service dev
|
||||
|
||||
# Terminal 2: Frontend
|
||||
pnpm --filter @goodgo/web-admin dev
|
||||
|
||||
# Terminal 3: Infrastructure
|
||||
cd deployments/local && docker-compose up -d redis traefik
|
||||
```
|
||||
|
||||
#### Use Case 3: Full Stack Development
|
||||
|
||||
```bash
|
||||
# Terminal 1: All services
|
||||
./scripts/dev/start-all.sh
|
||||
|
||||
# Terminal 2: Watch tests
|
||||
pnpm test --watch
|
||||
|
||||
# Terminal 3: Tools
|
||||
# Prisma Studio, logs, migrations, etc.
|
||||
```
|
||||
|
||||
## Kiểm Tra Health
|
||||
|
||||
### Health Endpoints
|
||||
|
||||
```bash
|
||||
# Kiểm tra API Gateway
|
||||
curl http://localhost/api/v1/health
|
||||
|
||||
# Kiểm tra Auth Service trực tiếp
|
||||
curl http://localhost:5001/health
|
||||
|
||||
# Kiểm tra Redis
|
||||
docker exec redis-cache-local redis-cli ping
|
||||
```
|
||||
|
||||
### Traefik Dashboard
|
||||
|
||||
Truy cập http://localhost:8080 để xem:
|
||||
- Tất cả routes đang hoạt động
|
||||
- Services đã đăng ký
|
||||
- Health status của services
|
||||
|
||||
## Database Development
|
||||
|
||||
### Thay Đổi Schema
|
||||
|
||||
```bash
|
||||
# 1. Chỉnh sửa prisma/schema.prisma
|
||||
# 2. Tạo và áp dụng migration
|
||||
cd services/auth-service
|
||||
pnpm prisma migrate dev --name add_new_field
|
||||
|
||||
# 3. Prisma Client sẽ tự động regenerate
|
||||
```
|
||||
|
||||
### Reset Database (Development Only!)
|
||||
|
||||
```bash
|
||||
cd services/auth-service
|
||||
pnpm prisma migrate reset
|
||||
```
|
||||
|
||||
### Xem Database
|
||||
|
||||
```bash
|
||||
# Mở Prisma Studio
|
||||
cd services/auth-service
|
||||
pnpm prisma studio
|
||||
# Truy cập: http://localhost:5555
|
||||
```
|
||||
|
||||
## Debugging
|
||||
|
||||
### VS Code Debugging
|
||||
|
||||
Tạo file `.vscode/launch.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Debug Auth Service",
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"runtimeExecutable": "pnpm",
|
||||
"runtimeArgs": ["--filter", "@goodgo/auth-service", "dev"],
|
||||
"skipFiles": ["<node_internals>/**"],
|
||||
"console": "integratedTerminal"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Xem Logs Chi Tiết
|
||||
|
||||
```bash
|
||||
# Service logs
|
||||
./scripts/dev/logs.sh auth-service
|
||||
|
||||
# Docker logs
|
||||
docker logs -f auth-service-local
|
||||
docker logs -f redis-cache-local
|
||||
docker logs -f traefik-local
|
||||
|
||||
# Tất cả logs
|
||||
docker-compose -f deployments/local/docker-compose.yml logs -f
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Port Đã Được Sử Dụng
|
||||
|
||||
```bash
|
||||
# Tìm process đang dùng port
|
||||
lsof -i :5001 # Auth service
|
||||
lsof -i :3000 # Web admin
|
||||
lsof -i :6379 # Redis
|
||||
lsof -i :80 # Traefik
|
||||
|
||||
# Kill process
|
||||
kill -9 <PID>
|
||||
```
|
||||
|
||||
### Docker Không Chạy
|
||||
|
||||
```bash
|
||||
# Kiểm tra Docker
|
||||
docker info
|
||||
|
||||
# Khởi động Docker Desktop (macOS)
|
||||
open -a Docker
|
||||
|
||||
# Restart Docker services
|
||||
docker-compose -f deployments/local/docker-compose.yml restart
|
||||
```
|
||||
|
||||
### Database Connection Failed
|
||||
|
||||
```bash
|
||||
# Kiểm tra DATABASE_URL trong service-specific env
|
||||
cat services/auth-service/.env.local | grep DATABASE_URL
|
||||
|
||||
# Nếu chưa có file .env.local, tạo từ example
|
||||
cp services/auth-service/env.local.example services/auth-service/.env.local
|
||||
|
||||
# Chỉnh sửa DATABASE_URL với connection string từ Neon
|
||||
# DATABASE_URL=postgresql://user:pass@ep-xxx.neon.tech/goodgo_auth_dev?sslmode=require&pgbouncer=true
|
||||
|
||||
# Test connection
|
||||
cd services/auth-service
|
||||
pnpm prisma db pull
|
||||
```
|
||||
|
||||
**Lưu ý**: Mỗi service cần file `.env.local` riêng với DATABASE_URL của service đó.
|
||||
|
||||
### Module Not Found
|
||||
|
||||
```bash
|
||||
# Cleanup và reinstall
|
||||
./scripts/utils/cleanup.sh
|
||||
pnpm install
|
||||
|
||||
# Hoặc chỉ cleanup node_modules
|
||||
rm -rf node_modules
|
||||
rm -rf services/*/node_modules
|
||||
rm -rf apps/*/node_modules
|
||||
rm -rf packages/*/node_modules
|
||||
pnpm install
|
||||
```
|
||||
|
||||
### Hot Reload Không Hoạt Động
|
||||
|
||||
```bash
|
||||
# Restart service
|
||||
# Ctrl+C để dừng, sau đó:
|
||||
pnpm dev
|
||||
|
||||
# Hoặc restart Docker container
|
||||
docker-compose -f deployments/local/docker-compose.yml restart auth-service
|
||||
```
|
||||
|
||||
## Tips & Best Practices
|
||||
|
||||
### 1. Sử Dụng Turbo Cache
|
||||
|
||||
Turbo cache giúp build nhanh hơn:
|
||||
|
||||
```bash
|
||||
# Lần đầu chạy sẽ chậm
|
||||
pnpm dev
|
||||
|
||||
# Các lần sau sẽ nhanh hơn nhờ cache
|
||||
# Cache được lưu trong node_modules/.cache/turbo
|
||||
```
|
||||
|
||||
### 2. Dev Selective Services
|
||||
|
||||
Không cần chạy tất cả nếu chỉ làm 1 service:
|
||||
|
||||
```bash
|
||||
# Chỉ chạy auth-service
|
||||
pnpm --filter @goodgo/auth-service dev
|
||||
|
||||
# Chạy auth-service và dependencies
|
||||
pnpm --filter @goodgo/auth-service... dev
|
||||
```
|
||||
|
||||
### 3. Watch Tests
|
||||
|
||||
```bash
|
||||
# Chạy tests tự động khi code thay đổi
|
||||
pnpm --filter @goodgo/auth-service test --watch
|
||||
```
|
||||
|
||||
### 4. Format Code Tự Động
|
||||
|
||||
Cài đặt Prettier extension trong VS Code và bật format on save.
|
||||
|
||||
### 5. Sử Dụng Git Hooks
|
||||
|
||||
```bash
|
||||
# Pre-commit hook sẽ tự động format và lint
|
||||
git commit -m "feat: add new feature"
|
||||
```
|
||||
|
||||
### 6. Hybrid Development (Best of Both Worlds)
|
||||
|
||||
Kết hợp Docker và Native để tối ưu workflow:
|
||||
|
||||
```bash
|
||||
# Infrastructure luôn chạy Docker
|
||||
cd deployments/local && docker-compose up -d redis traefik
|
||||
|
||||
# Service đang dev chạy native (hot reload nhanh)
|
||||
pnpm --filter @goodgo/auth-service dev
|
||||
|
||||
# Services khác có thể chạy Docker nếu cần
|
||||
docker-compose up -d user-service payment-service
|
||||
```
|
||||
|
||||
**Lợi ích:**
|
||||
- ⚡ Hot reload nhanh nhất cho service đang làm
|
||||
- 💻 Tiết kiệm RAM - không chạy tất cả containers
|
||||
- 🐛 Debug dễ dàng - breakpoints, logs trực tiếp
|
||||
- 🎯 Linh hoạt - chọn service nào chạy native
|
||||
|
||||
### 7. Quản Lý Multiple Services
|
||||
|
||||
```bash
|
||||
# Chạy selective services với pnpm workspace
|
||||
pnpm --filter "@goodgo/auth-service" --filter "@goodgo/user-service" dev
|
||||
|
||||
# Hoặc dùng pattern
|
||||
pnpm --filter "./services/{auth,user}-service" dev
|
||||
```
|
||||
|
||||
## Environment Variables
|
||||
|
||||
Dự án sử dụng **Hybrid Environment Configuration** với 2 levels:
|
||||
|
||||
### Level 1: Shared Environment (`deployments/local/.env.local`)
|
||||
|
||||
Configs dùng chung cho tất cả services:
|
||||
|
||||
```bash
|
||||
# JWT Secrets - MUST be same across all services
|
||||
JWT_SECRET=dev-jwt-secret-change-in-production-min-32-chars
|
||||
JWT_REFRESH_SECRET=dev-refresh-secret-change-in-production-min-32-chars
|
||||
JWT_EXPIRES_IN=15m
|
||||
JWT_REFRESH_EXPIRES_IN=7d
|
||||
|
||||
# Redis (Docker hostname)
|
||||
REDIS_HOST=redis
|
||||
REDIS_PORT=6379
|
||||
|
||||
# Common configs
|
||||
NODE_ENV=development
|
||||
LOG_LEVEL=debug
|
||||
CORS_ORIGIN=http://localhost:3000,http://localhost:3001
|
||||
|
||||
# Monitoring (optional)
|
||||
TRACING_ENABLED=false
|
||||
```
|
||||
|
||||
**Tạo file:**
|
||||
```bash
|
||||
cp deployments/local/env.local.example deployments/local/.env.local
|
||||
```
|
||||
|
||||
### Level 2: Service-Specific Environment (`services/<service>/.env.local`)
|
||||
|
||||
Configs riêng cho từng service:
|
||||
|
||||
```bash
|
||||
# services/auth-service/.env.local
|
||||
|
||||
# Database riêng cho service này
|
||||
DATABASE_URL=postgresql://user:pass@ep-xxx.neon.tech/goodgo_auth_dev?sslmode=require&pgbouncer=true
|
||||
|
||||
# Service configs
|
||||
PORT=5001
|
||||
SERVICE_NAME=auth-service
|
||||
API_VERSION=v1
|
||||
|
||||
# Redis override (native dev - Redis in Docker)
|
||||
REDIS_HOST=localhost
|
||||
|
||||
# Service-specific configs
|
||||
EMAIL_SERVICE_URL=http://notification-service:5003
|
||||
PROMETHEUS_PORT=9090
|
||||
```
|
||||
|
||||
**Tạo file:**
|
||||
```bash
|
||||
cp services/auth-service/env.local.example services/auth-service/.env.local
|
||||
```
|
||||
|
||||
### Cách Hoạt Động
|
||||
|
||||
Services load env theo thứ tự:
|
||||
1. **Shared env** (`deployments/local/.env.local`) - JWT, Redis, common configs
|
||||
2. **Service env** (`.env.local`) - DATABASE_URL, PORT, overrides
|
||||
|
||||
```bash
|
||||
# Trong package.json
|
||||
"dev": "dotenv -e ../../deployments/local/.env.local -e .env.local -- tsx watch src/main.ts"
|
||||
```
|
||||
|
||||
**Lợi ích:**
|
||||
- ✅ JWT secrets giống nhau → services verify tokens của nhau
|
||||
- ✅ Mỗi service có database riêng → microservices pattern
|
||||
- ✅ Override configs dễ dàng → REDIS_HOST=localhost cho native dev
|
||||
- ✅ Không duplicate configs → maintain dễ hơn
|
||||
|
||||
### Important Notes
|
||||
|
||||
1. **JWT Secrets**: MUST be identical across all services
|
||||
2. **Database**: Each service has its own database (e.g., `goodgo_auth_dev`, `goodgo_user_dev`)
|
||||
3. **Redis Host**:
|
||||
- `redis` (Docker hostname) in shared env
|
||||
- `localhost` (override) in service env for native dev
|
||||
4. **Never commit**: `.env.local` files are gitignored
|
||||
|
||||
## Các Lệnh Hữu Ích
|
||||
|
||||
```bash
|
||||
# Development
|
||||
pnpm dev # Chạy tất cả services
|
||||
pnpm build # Build tất cả
|
||||
pnpm test # Test tất cả
|
||||
pnpm lint # Lint tất cả
|
||||
pnpm format # Format code
|
||||
|
||||
# Cleanup
|
||||
pnpm clean # Xóa build artifacts
|
||||
./scripts/utils/cleanup.sh # Cleanup toàn bộ
|
||||
|
||||
# Database
|
||||
./scripts/db/migrate.sh auth-service dev # Migration
|
||||
./scripts/db/seed.sh auth-service # Seed data
|
||||
./scripts/db/backup.sh auth-service # Backup
|
||||
|
||||
# Docker
|
||||
docker-compose -f deployments/local/docker-compose.yml up -d # Start
|
||||
docker-compose -f deployments/local/docker-compose.yml down # Stop
|
||||
docker-compose -f deployments/local/docker-compose.yml logs -f # Logs
|
||||
docker-compose -f deployments/local/docker-compose.yml restart # Restart
|
||||
```
|
||||
|
||||
## Tài Nguyên Thêm
|
||||
|
||||
- [Getting Started](getting-started.md) - Thiết lập ban đầu
|
||||
- [Development Guide](development.md) - Quy trình development
|
||||
- [Neon Database Guide](neon-database.md) - Hướng dẫn database
|
||||
- [Troubleshooting](troubleshooting.md) - Xử lý sự cố
|
||||
215
docs/vi/guides/neon-database.md
Normal file
215
docs/vi/guides/neon-database.md
Normal file
@@ -0,0 +1,215 @@
|
||||
# Hướng Dẫn Neon Database
|
||||
|
||||
Project này sử dụng [Neon PostgreSQL](https://neon.tech) cho tất cả môi trường.
|
||||
|
||||
## Tại Sao Chọn Neon?
|
||||
|
||||
- ✅ **Serverless**: Không cần quản lý infrastructure
|
||||
- ✅ **Branching**: Database riêng cho dev/staging/prod
|
||||
- ✅ **Auto-scaling**: Tự động xử lý traffic spikes
|
||||
- ✅ **Point-in-time restore**: Dễ dàng khôi phục từ lỗi
|
||||
- ✅ **Free tier**: Hoàn hảo cho development
|
||||
- ✅ **Connection pooling**: Hỗ trợ PgBouncer tích hợp
|
||||
|
||||
## Bắt Đầu Nhanh
|
||||
|
||||
### 1. Tạo Tài Khoản Neon
|
||||
|
||||
1. Đăng ký tại https://neon.tech
|
||||
2. Tạo project mới: `goodgo-platform`
|
||||
|
||||
### 2. Tạo Branches
|
||||
|
||||
Trong Neon Console, tạo branches:
|
||||
- `main` (development) - đã tồn tại
|
||||
- `staging` - tạo từ main
|
||||
- `production` - tạo từ main
|
||||
|
||||
### 3. Lấy Connection Strings
|
||||
|
||||
Cho mỗi branch, copy connection string:
|
||||
- Định dạng: `postgresql://user:password@ep-xxx.region.neon.tech/dbname?sslmode=require`
|
||||
- Thêm `?pgbouncer=true` cho connection pooling (khuyến nghị)
|
||||
|
||||
### 4. Cấu Hình Local Development
|
||||
|
||||
```bash
|
||||
# Tạo .env.local
|
||||
cp deployments/local/env.local.example deployments/local/.env.local
|
||||
|
||||
# Chỉnh sửa .env.local và thêm:
|
||||
DATABASE_URL=postgresql://user:pass@ep-xxx.region.neon.tech/dbname?sslmode=require&pgbouncer=true
|
||||
```
|
||||
|
||||
### 5. Chạy Migrations
|
||||
|
||||
```bash
|
||||
./scripts/db/migrate.sh auth-service dev
|
||||
```
|
||||
|
||||
## Định Dạng Connection String
|
||||
|
||||
```
|
||||
postgresql://[user]:[password]@[endpoint]/[dbname]?sslmode=require&pgbouncer=true
|
||||
```
|
||||
|
||||
**Tham số**:
|
||||
- `sslmode=require` - Bắt buộc cho Neon
|
||||
- `pgbouncer=true` - Bật connection pooling (khuyến nghị)
|
||||
|
||||
## Cấu Hình Môi Trường
|
||||
|
||||
### Local Development
|
||||
|
||||
File: `deployments/local/.env.local`
|
||||
|
||||
```bash
|
||||
DATABASE_URL=postgresql://user:pass@ep-xxx.region.neon.tech/dbname?sslmode=require&pgbouncer=true
|
||||
```
|
||||
|
||||
### Staging
|
||||
|
||||
Lưu trong GitHub Secrets: `NEON_DATABASE_URL_STAGING`
|
||||
|
||||
Hoặc trong Kubernetes:
|
||||
```bash
|
||||
kubectl create secret generic auth-service-secrets \
|
||||
--from-literal=database-url='postgresql://...' \
|
||||
-n staging
|
||||
```
|
||||
|
||||
### Production
|
||||
|
||||
Lưu trong GitHub Secrets: `NEON_DATABASE_URL_PRODUCTION`
|
||||
|
||||
Hoặc trong Kubernetes:
|
||||
```bash
|
||||
kubectl create secret generic auth-service-secrets \
|
||||
--from-literal=database-url='postgresql://...' \
|
||||
-n production
|
||||
```
|
||||
|
||||
## Migrations
|
||||
|
||||
### Development
|
||||
|
||||
```bash
|
||||
# Tạo migration mới
|
||||
./scripts/db/migrate.sh auth-service dev
|
||||
|
||||
# Điều này sẽ:
|
||||
# 1. Tạo migration file
|
||||
# 2. Áp dụng vào database
|
||||
# 3. Cập nhật Prisma Client
|
||||
```
|
||||
|
||||
### Staging/Production
|
||||
|
||||
Migrations chạy tự động trong CI/CD:
|
||||
- Trước khi deploy lên staging
|
||||
- Trước khi deploy lên production (cần approval)
|
||||
|
||||
Migration thủ công:
|
||||
```bash
|
||||
./scripts/db/migrate.sh auth-service deploy
|
||||
```
|
||||
|
||||
## Backup & Restore
|
||||
|
||||
### Backup Tự Động
|
||||
|
||||
Neon cung cấp backup tự động. Truy cập qua Neon Console:
|
||||
- Point-in-time restore
|
||||
- Branch restore
|
||||
- Export data
|
||||
|
||||
### Backup Thủ Công
|
||||
|
||||
```bash
|
||||
./scripts/db/backup.sh auth-service
|
||||
```
|
||||
|
||||
Điều này tạo file SQL dump trong thư mục `backups/`.
|
||||
|
||||
### Restore
|
||||
|
||||
```bash
|
||||
# Từ Neon Console (khuyến nghị)
|
||||
# Hoặc sử dụng psql:
|
||||
psql $DATABASE_URL < backup.sql
|
||||
```
|
||||
|
||||
## Monitoring
|
||||
|
||||
Theo dõi databases qua Neon Console:
|
||||
- Connection metrics
|
||||
- Query performance
|
||||
- Storage usage
|
||||
- Branch status
|
||||
|
||||
## Xử Lý Sự Cố
|
||||
|
||||
### Vấn Đề Kết Nối
|
||||
|
||||
1. **Kiểm tra định dạng connection string**
|
||||
- Phải bao gồm `?sslmode=require`
|
||||
- Xác minh credentials
|
||||
|
||||
2. **Kiểm tra IP allowlist**
|
||||
- Neon có thể giới hạn IPs
|
||||
- Thêm IP của bạn trong Neon Console
|
||||
|
||||
3. **Kiểm tra branch status**
|
||||
- Đảm bảo branch đang active
|
||||
- Kiểm tra maintenance
|
||||
|
||||
### Vấn Đề Migration
|
||||
|
||||
1. **DATABASE_URL chưa set**
|
||||
```bash
|
||||
export DATABASE_URL="your-neon-url"
|
||||
```
|
||||
|
||||
2. **Schema không khớp**
|
||||
```bash
|
||||
# Reset và migrate lại (chỉ dev!)
|
||||
pnpm prisma migrate reset
|
||||
```
|
||||
|
||||
3. **Connection timeout**
|
||||
- Thêm `?pgbouncer=true` cho pooling
|
||||
- Kiểm tra Neon console cho limits
|
||||
|
||||
### Vấn Đề Hiệu Suất
|
||||
|
||||
1. **Bật connection pooling**
|
||||
- Thêm `?pgbouncer=true` vào connection string
|
||||
|
||||
2. **Kiểm tra query performance**
|
||||
- Sử dụng Neon Console query analyzer
|
||||
- Xem lại slow queries
|
||||
|
||||
3. **Tối ưu indexes**
|
||||
- Xem lại Prisma schema
|
||||
- Thêm indexes cho các query thường dùng
|
||||
|
||||
## Tối Ưu Chi Phí
|
||||
|
||||
- **Free tier**: 0.5 GB storage, đủ cho dev
|
||||
- **Staging**: Sử dụng free tier hoặc plan trả phí tối thiểu
|
||||
- **Production**: Scale dựa trên usage
|
||||
- **Branching**: Free branches cho testing
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Luôn sử dụng connection pooling**: `?pgbouncer=true`
|
||||
2. **Sử dụng SSL**: `?sslmode=require`
|
||||
3. **Tách branches**: Một branch cho mỗi môi trường
|
||||
4. **Backup thường xuyên**: Sử dụng automatic backups của Neon
|
||||
5. **Theo dõi usage**: Kiểm tra Neon Console thường xuyên
|
||||
|
||||
## Tài Nguyên
|
||||
|
||||
- [Neon Documentation](https://neon.tech/docs)
|
||||
- [Neon Console](https://console.neon.tech)
|
||||
- [Prisma + Neon Guide](https://neon.tech/docs/guides/prisma)
|
||||
57
docs/vi/guides/troubleshooting.md
Normal file
57
docs/vi/guides/troubleshooting.md
Normal file
@@ -0,0 +1,57 @@
|
||||
# Hướng Dẫn Xử Lý Sự Cố
|
||||
|
||||
## Các Vấn Đề Thường Gặp
|
||||
|
||||
### Kết Nối Database Thất Bại
|
||||
|
||||
**Triệu chứng**: Service không thể kết nối database
|
||||
|
||||
**Giải pháp**:
|
||||
1. Kiểm tra PostgreSQL có đang chạy: `docker ps`
|
||||
2. Xác minh DATABASE_URL trong .env
|
||||
3. Kiểm tra kết nối mạng: `docker network ls`
|
||||
4. Xem logs: `docker logs postgres-auth-local`
|
||||
|
||||
### Port Đã Được Sử Dụng
|
||||
|
||||
**Triệu chứng**: Service không khởi động với lỗi port
|
||||
|
||||
**Giải pháp**:
|
||||
1. Tìm process đang dùng port: `lsof -i :5001`
|
||||
2. Kill process hoặc thay đổi PORT trong .env
|
||||
3. Kiểm tra docker-compose cho port conflicts
|
||||
|
||||
### Prisma Client Chưa Được Generate
|
||||
|
||||
**Triệu chứng**: Lỗi import cho Prisma Client
|
||||
|
||||
**Giải pháp**:
|
||||
```bash
|
||||
cd services/auth-service
|
||||
pnpm prisma generate
|
||||
```
|
||||
|
||||
### Build Failures
|
||||
|
||||
**Triệu chứng**: Lỗi TypeScript hoặc build
|
||||
|
||||
**Giải pháp**:
|
||||
1. Xóa build artifacts: `./scripts/utils/cleanup.sh`
|
||||
2. Cài đặt lại dependencies: `pnpm install`
|
||||
3. Kiểm tra lỗi TypeScript: `pnpm typecheck`
|
||||
|
||||
### Traefik Không Routing
|
||||
|
||||
**Triệu chứng**: Lỗi 404 từ Traefik
|
||||
|
||||
**Giải pháp**:
|
||||
1. Kiểm tra Traefik dashboard: http://localhost:8080
|
||||
2. Xác minh service labels trong docker-compose
|
||||
3. Kiểm tra cấu hình routes.yml
|
||||
4. Xem Traefik logs: `docker logs traefik-local`
|
||||
|
||||
## Tìm Kiếm Trợ Giúp
|
||||
|
||||
1. Kiểm tra service logs: `./scripts/dev/logs.sh <service>`
|
||||
2. Xem lại GitHub Issues
|
||||
3. Liên hệ team lead
|
||||
89
docs/vi/onboarding/new-developer-guide.md
Normal file
89
docs/vi/onboarding/new-developer-guide.md
Normal file
@@ -0,0 +1,89 @@
|
||||
# Hướng Dẫn Cho Developer Mới
|
||||
|
||||
Chào mừng đến với team GoodGo Microservices Platform!
|
||||
|
||||
## Checklist Ngày Đầu Tiên
|
||||
|
||||
- [ ] Quyền truy cập GitHub repository
|
||||
- [ ] Quyền truy cập môi trường development
|
||||
- [ ] Docker đã cài đặt và đang chạy
|
||||
- [ ] Node.js và PNPM đã cài đặt
|
||||
- [ ] IDE đã cấu hình (khuyến nghị VS Code)
|
||||
- [ ] Đã đọc hướng dẫn này
|
||||
|
||||
## Thiết Lập Môi Trường Development
|
||||
|
||||
1. **Clone repository**
|
||||
```bash
|
||||
git clone <repository-url>
|
||||
cd Base
|
||||
```
|
||||
|
||||
2. **Chạy script khởi tạo**
|
||||
```bash
|
||||
./scripts/setup/init-project.sh
|
||||
```
|
||||
|
||||
3. **Khởi động infrastructure local**
|
||||
```bash
|
||||
cd deployments/local
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
4. **Xác minh setup**
|
||||
- Kiểm tra Traefik: http://localhost:8080
|
||||
- Kiểm tra API: http://localhost/api/v1/health
|
||||
|
||||
## Công Cụ Development
|
||||
|
||||
### VS Code Extensions Khuyến Nghị
|
||||
|
||||
- ESLint
|
||||
- Prettier
|
||||
- Prisma
|
||||
- Docker
|
||||
- GitLens
|
||||
|
||||
### Các Lệnh Hữu Ích
|
||||
|
||||
```bash
|
||||
# Khởi động tất cả services
|
||||
./scripts/dev/start-all.sh
|
||||
|
||||
# Khởi động service cụ thể
|
||||
./scripts/dev/start-service.sh auth-service
|
||||
|
||||
# Xem logs
|
||||
./scripts/dev/logs.sh auth-service
|
||||
|
||||
# Chạy migrations
|
||||
./scripts/db/migrate.sh auth-service dev
|
||||
|
||||
# Chạy tests
|
||||
pnpm test
|
||||
```
|
||||
|
||||
## Tiêu Chuẩn Code
|
||||
|
||||
- **TypeScript**: Bật strict mode
|
||||
- **Linting**: ESLint với shared config
|
||||
- **Formatting**: Prettier
|
||||
- **Commits**: Định dạng Conventional Commits
|
||||
- **Tests**: Tối thiểu 80% coverage
|
||||
|
||||
## Tìm Kiếm Trợ Giúp
|
||||
|
||||
- Xem [Tài Liệu](../guides/)
|
||||
- Hỏi trong Slack channel của team
|
||||
- Xem lại các ví dụ code hiện có
|
||||
- Pair với senior developer
|
||||
|
||||
## Các Bước Tiếp Theo
|
||||
|
||||
1. Chọn một task nhỏ từ backlog
|
||||
2. Tạo feature branch
|
||||
3. Implement và test
|
||||
4. Tạo pull request
|
||||
5. Nhận code review
|
||||
|
||||
Chúc may mắn! 🚀
|
||||
65
docs/vi/runbooks/incident-response.md
Normal file
65
docs/vi/runbooks/incident-response.md
Normal file
@@ -0,0 +1,65 @@
|
||||
# Runbook Phản Ứng Sự Cố
|
||||
|
||||
## Mức Độ Nghiêm Trọng
|
||||
|
||||
- **P0 - Nghiêm Trọng**: Dịch vụ hoàn toàn ngừng hoạt động, mất dữ liệu
|
||||
- **P1 - Cao**: Chức năng chính bị lỗi, ảnh hưởng đến nhiều người dùng
|
||||
- **P2 - Trung Bình**: Chức năng phụ bị lỗi, có giải pháp thay thế
|
||||
- **P3 - Thấp**: Lỗi giao diện, không ảnh hưởng người dùng
|
||||
|
||||
## Quy Trình Phản Ứng
|
||||
|
||||
### 1. Xác Nhận Sự Cố
|
||||
|
||||
- Xác định mức độ nghiêm trọng
|
||||
- Thông báo cho team qua Slack/email
|
||||
- Tạo ticket sự cố
|
||||
|
||||
### 2. Điều Tra
|
||||
|
||||
- Kiểm tra các endpoint health của service
|
||||
- Xem logs: `./scripts/dev/logs.sh <service>`
|
||||
- Kiểm tra monitoring dashboards (Grafana)
|
||||
- Xem lại các deployment gần đây
|
||||
|
||||
### 3. Giảm Thiểu
|
||||
|
||||
- Áp dụng các sửa lỗi nhanh nếu có
|
||||
- Rollback nếu deployment gần đây gây ra vấn đề
|
||||
- Scale up nếu thiếu tài nguyên
|
||||
|
||||
### 4. Giải Quyết
|
||||
|
||||
- Triển khai sửa lỗi vĩnh viễn
|
||||
- Xác minh giải pháp
|
||||
- Cập nhật tài liệu
|
||||
|
||||
### 5. Hậu Phân Tích
|
||||
|
||||
- Ghi lại sự cố
|
||||
- Xác định nguyên nhân gốc rễ
|
||||
- Tạo các hành động cần thực hiện
|
||||
- Cập nhật runbooks
|
||||
|
||||
## Các Tình Huống Thường Gặp
|
||||
|
||||
### Service Ngừng Hoạt Động
|
||||
|
||||
1. Kiểm tra Kubernetes pods: `kubectl get pods -n <namespace>`
|
||||
2. Kiểm tra pod logs: `kubectl logs <pod-name> -n <namespace>`
|
||||
3. Khởi động lại service: `kubectl rollout restart deployment/<service> -n <namespace>`
|
||||
4. Nếu vẫn lỗi, rollback: `kubectl rollout undo deployment/<service> -n <namespace>`
|
||||
|
||||
### Vấn Đề Database
|
||||
|
||||
1. Kiểm tra kết nối database
|
||||
2. Xem lại các query chậm
|
||||
3. Kiểm tra connection pool
|
||||
4. Scale database nếu cần
|
||||
|
||||
### Tỷ Lệ Lỗi Cao
|
||||
|
||||
1. Kiểm tra error logs
|
||||
2. Xem lại các thay đổi gần đây
|
||||
3. Kiểm tra các dependencies bên ngoài
|
||||
4. Triển khai circuit breaker nếu cần
|
||||
71
docs/vi/runbooks/rollback-procedure.md
Normal file
71
docs/vi/runbooks/rollback-procedure.md
Normal file
@@ -0,0 +1,71 @@
|
||||
# Quy Trình Rollback
|
||||
|
||||
## Khi Nào Cần Rollback
|
||||
|
||||
- Service ngừng hoạt động hoặc không ổn định
|
||||
- Lỗi nghiêm trọng được phát hiện
|
||||
- Hiệu suất suy giảm
|
||||
- Rủi ro hỏng dữ liệu
|
||||
|
||||
## Các Bước Rollback
|
||||
|
||||
### Rollback Kubernetes
|
||||
|
||||
1. **Xác định phiên bản hiện tại**
|
||||
```bash
|
||||
kubectl get deployment auth-service -n production -o jsonpath='{.spec.template.spec.containers[0].image}'
|
||||
```
|
||||
|
||||
2. **Rollback về phiên bản trước**
|
||||
```bash
|
||||
kubectl rollout undo deployment/auth-service -n production
|
||||
```
|
||||
|
||||
3. **Xác minh rollback**
|
||||
```bash
|
||||
kubectl rollout status deployment/auth-service -n production
|
||||
```
|
||||
|
||||
4. **Kiểm tra health của service**
|
||||
```bash
|
||||
curl https://api.goodgo.vn/health
|
||||
```
|
||||
|
||||
### Rollback Database Migration
|
||||
|
||||
**Lưu ý**: Prisma không hỗ trợ rollback tự động. Tạo migration mới để đảo ngược thay đổi.
|
||||
|
||||
1. Tạo migration đảo ngược:
|
||||
```bash
|
||||
cd services/auth-service
|
||||
pnpm prisma migrate dev --name rollback_previous_change
|
||||
```
|
||||
|
||||
2. Áp dụng migration đảo ngược:
|
||||
```bash
|
||||
pnpm prisma migrate deploy
|
||||
```
|
||||
|
||||
### Rollback Docker Compose
|
||||
|
||||
1. Dừng các container hiện tại:
|
||||
```bash
|
||||
docker-compose down
|
||||
```
|
||||
|
||||
2. Checkout phiên bản trước:
|
||||
```bash
|
||||
git checkout <previous-commit>
|
||||
```
|
||||
|
||||
3. Rebuild và khởi động:
|
||||
```bash
|
||||
docker-compose up -d --build
|
||||
```
|
||||
|
||||
## Sau Khi Rollback
|
||||
|
||||
1. Xác minh chức năng
|
||||
2. Theo dõi metrics
|
||||
3. Ghi lại lý do rollback
|
||||
4. Lập kế hoạch sửa lỗi cho deployment tiếp theo
|
||||
48
infra/databases/docker-compose.databases.yml
Normal file
48
infra/databases/docker-compose.databases.yml
Normal file
@@ -0,0 +1,48 @@
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:16-alpine
|
||||
container_name: postgres-auth
|
||||
environment:
|
||||
POSTGRES_USER: ${POSTGRES_USER:-devuser}
|
||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-devpass}
|
||||
POSTGRES_DB: ${POSTGRES_DB:-auth_db}
|
||||
ports:
|
||||
- "${POSTGRES_PORT:-5432}:5432"
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql/data
|
||||
- ./postgres/init.sql:/docker-entrypoint-initdb.d/init.sql
|
||||
- ./postgres/postgres.conf:/etc/postgresql/postgresql.conf
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-devuser}"]
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
networks:
|
||||
- microservices-network
|
||||
|
||||
redis:
|
||||
image: redis:7-alpine
|
||||
container_name: redis-cache
|
||||
command: redis-server /etc/redis/redis.conf
|
||||
ports:
|
||||
- "${REDIS_PORT:-6379}:6379"
|
||||
volumes:
|
||||
- redis_data:/data
|
||||
- ./redis/redis.conf:/etc/redis/redis.conf
|
||||
healthcheck:
|
||||
test: ["CMD", "redis-cli", "ping"]
|
||||
interval: 5s
|
||||
timeout: 3s
|
||||
retries: 5
|
||||
networks:
|
||||
- microservices-network
|
||||
|
||||
volumes:
|
||||
postgres_data:
|
||||
redis_data:
|
||||
|
||||
networks:
|
||||
microservices-network:
|
||||
external: true
|
||||
125
infra/databases/neon/README.md
Normal file
125
infra/databases/neon/README.md
Normal file
@@ -0,0 +1,125 @@
|
||||
# Neon Database Setup
|
||||
|
||||
This project uses [Neon PostgreSQL](https://neon.tech) for all environments (dev, staging, production).
|
||||
|
||||
## Why Neon?
|
||||
|
||||
- ✅ Serverless PostgreSQL with auto-scaling
|
||||
- ✅ Branching support (dev, staging, production branches)
|
||||
- ✅ Point-in-time restore
|
||||
- ✅ Free tier for development
|
||||
- ✅ Built-in connection pooling
|
||||
- ✅ No infrastructure management needed
|
||||
|
||||
## Setup Instructions
|
||||
|
||||
### 1. Create Neon Project
|
||||
|
||||
1. Sign up at https://neon.tech
|
||||
2. Create a new project: `goodgo-platform`
|
||||
3. Note your connection string from the main branch
|
||||
|
||||
### 2. Create Branches
|
||||
|
||||
Create branches for each environment:
|
||||
|
||||
```bash
|
||||
# Using Neon Console or CLI
|
||||
# Main branch = development
|
||||
# Create: staging branch
|
||||
# Create: production branch
|
||||
```
|
||||
|
||||
### 3. Get Connection Strings
|
||||
|
||||
For each branch, get the connection string:
|
||||
- Format: `postgresql://user:password@ep-xxx.region.neon.tech/dbname?sslmode=require`
|
||||
- Add `?pgbouncer=true` for connection pooling (recommended)
|
||||
|
||||
### 4. Configure Environment Variables
|
||||
|
||||
#### Local Development
|
||||
|
||||
Create `deployments/local/.env.local`:
|
||||
|
||||
```bash
|
||||
DATABASE_URL=postgresql://user:pass@ep-xxx.region.neon.tech/dbname?sslmode=require&pgbouncer=true
|
||||
```
|
||||
|
||||
#### Staging/Production
|
||||
|
||||
Store in GitHub Secrets:
|
||||
- `NEON_DATABASE_URL_STAGING`
|
||||
- `NEON_DATABASE_URL_PRODUCTION`
|
||||
|
||||
Or in Kubernetes Secrets (see `deployments/staging/kubernetes/secrets.yaml.example`)
|
||||
|
||||
## Connection Pooling
|
||||
|
||||
Neon provides built-in connection pooling. Add `?pgbouncer=true` to your connection string:
|
||||
|
||||
```
|
||||
postgresql://user:pass@ep-xxx.region.neon.tech/dbname?sslmode=require&pgbouncer=true
|
||||
```
|
||||
|
||||
## Migration
|
||||
|
||||
### Development
|
||||
|
||||
```bash
|
||||
cd services/auth-service
|
||||
pnpm prisma migrate dev
|
||||
```
|
||||
|
||||
### Staging/Production
|
||||
|
||||
Migrations run automatically in CI/CD workflows before deployment.
|
||||
|
||||
## Backup & Restore
|
||||
|
||||
Neon provides automatic backups and point-in-time restore via their console.
|
||||
|
||||
For manual backup:
|
||||
|
||||
```bash
|
||||
# Using pg_dump
|
||||
pg_dump $DATABASE_URL > backup.sql
|
||||
|
||||
# Restore
|
||||
psql $DATABASE_URL < backup.sql
|
||||
```
|
||||
|
||||
## Monitoring
|
||||
|
||||
Monitor your databases via Neon Console:
|
||||
- Connection metrics
|
||||
- Query performance
|
||||
- Storage usage
|
||||
- Branch status
|
||||
|
||||
## Cost Optimization
|
||||
|
||||
- **Free tier**: Sufficient for development
|
||||
- **Staging**: Use free tier or minimal paid plan
|
||||
- **Production**: Scale based on usage
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Connection Issues
|
||||
|
||||
1. Check connection string format
|
||||
2. Verify SSL mode: `?sslmode=require`
|
||||
3. Check IP allowlist in Neon console (if enabled)
|
||||
4. Verify credentials
|
||||
|
||||
### Migration Issues
|
||||
|
||||
1. Ensure DATABASE_URL is set correctly
|
||||
2. Check Prisma schema matches database
|
||||
3. Review migration files
|
||||
|
||||
### Performance Issues
|
||||
|
||||
1. Enable connection pooling: `?pgbouncer=true`
|
||||
2. Check query performance in Neon console
|
||||
3. Review indexes in Prisma schema
|
||||
64
infra/databases/neon/setup.sh
Executable file
64
infra/databases/neon/setup.sh
Executable file
@@ -0,0 +1,64 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
echo "🚀 Neon Database Setup Script"
|
||||
echo ""
|
||||
|
||||
# Check if Neon CLI is installed
|
||||
if ! command -v neonctl &> /dev/null; then
|
||||
echo "⚠️ Neon CLI not found. Installing..."
|
||||
echo "Visit: https://neon.tech/docs/cli/install"
|
||||
echo ""
|
||||
read -p "Do you want to continue with manual setup? (y/n): " continue
|
||||
if [ "$continue" != "y" ]; then
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "This script will help you set up Neon database branches."
|
||||
echo ""
|
||||
echo "Prerequisites:"
|
||||
echo "1. Neon account created at https://neon.tech"
|
||||
echo "2. Neon project created"
|
||||
echo "3. Neon CLI installed (optional)"
|
||||
echo ""
|
||||
|
||||
read -p "Enter your Neon project name (default: goodgo-platform): " project_name
|
||||
project_name=${project_name:-goodgo-platform}
|
||||
|
||||
echo ""
|
||||
echo "📋 Setup Steps:"
|
||||
echo ""
|
||||
echo "1. Go to https://console.neon.tech"
|
||||
echo "2. Select your project: $project_name"
|
||||
echo "3. Create branches:"
|
||||
echo " - staging (from main)"
|
||||
echo " - production (from main)"
|
||||
echo ""
|
||||
echo "4. Get connection strings for each branch:"
|
||||
echo " - Development (main branch)"
|
||||
echo " - Staging branch"
|
||||
echo " - Production branch"
|
||||
echo ""
|
||||
echo "5. Update environment files:"
|
||||
echo " - deployments/local/.env.local"
|
||||
echo " - GitHub Secrets (for CI/CD)"
|
||||
echo " - Kubernetes Secrets (for deployments)"
|
||||
echo ""
|
||||
|
||||
read -p "Enter development DATABASE_URL (or press Enter to skip): " dev_url
|
||||
if [ -n "$dev_url" ]; then
|
||||
echo "DATABASE_URL=$dev_url" >> deployments/local/.env.local
|
||||
echo "✅ Added to deployments/local/.env.local"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "📝 Next steps:"
|
||||
echo ""
|
||||
echo "1. Add staging URL to GitHub Secrets: NEON_DATABASE_URL_STAGING"
|
||||
echo "2. Add production URL to GitHub Secrets: NEON_DATABASE_URL_PRODUCTION"
|
||||
echo "3. Create Kubernetes secrets for staging/production"
|
||||
echo "4. Run migrations: ./scripts/db/migrate.sh auth-service dev"
|
||||
echo ""
|
||||
echo "✅ Setup complete! See infra/databases/neon/README.md for details."
|
||||
8
infra/databases/postgres/init.sql
Normal file
8
infra/databases/postgres/init.sql
Normal file
@@ -0,0 +1,8 @@
|
||||
-- Initial database setup script
|
||||
-- This script runs when PostgreSQL container starts for the first time
|
||||
|
||||
-- Create extensions if needed
|
||||
-- CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
|
||||
|
||||
-- Note: Prisma migrations handle schema creation
|
||||
-- This file is for any additional setup needed
|
||||
15
infra/databases/postgres/postgres.conf
Normal file
15
infra/databases/postgres/postgres.conf
Normal file
@@ -0,0 +1,15 @@
|
||||
# PostgreSQL configuration
|
||||
# Customize as needed for your environment
|
||||
|
||||
max_connections = 200
|
||||
shared_buffers = 256MB
|
||||
effective_cache_size = 1GB
|
||||
maintenance_work_mem = 64MB
|
||||
checkpoint_completion_target = 0.9
|
||||
wal_buffers = 16MB
|
||||
default_statistics_target = 100
|
||||
random_page_cost = 1.1
|
||||
effective_io_concurrency = 200
|
||||
work_mem = 4MB
|
||||
min_wal_size = 1GB
|
||||
max_wal_size = 4GB
|
||||
21
infra/databases/redis/redis.conf
Normal file
21
infra/databases/redis/redis.conf
Normal file
@@ -0,0 +1,21 @@
|
||||
# Redis configuration
|
||||
|
||||
# Network
|
||||
bind 0.0.0.0
|
||||
protected-mode yes
|
||||
port 6379
|
||||
|
||||
# Memory
|
||||
maxmemory 256mb
|
||||
maxmemory-policy allkeys-lru
|
||||
|
||||
# Persistence
|
||||
save 900 1
|
||||
save 300 10
|
||||
save 60 10000
|
||||
|
||||
# Logging
|
||||
loglevel notice
|
||||
|
||||
# Security
|
||||
# requirepass your-redis-password-here
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user