diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index b9fc7940..f88c0515 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -1,25 +1,116 @@
-name: CI
+name: CI/CD Pipeline
on:
push:
- branches: [ main ]
+ branches: [ main, develop ]
pull_request:
- branches: [ main ]
+ branches: [ main, develop ]
jobs:
+ lint:
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v3
+
+ - name: Setup Node.js
+ uses: actions/setup-node@v3
+ with:
+ node-version: '18'
+ cache: 'npm'
+
+ - name: Install dependencies
+ run: npm ci
+
+ - name: Run ESLint
+ run: npm run lint
+
+ test:
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v3
+
+ - name: Setup Node.js
+ uses: actions/setup-node@v3
+ with:
+ node-version: '18'
+ cache: 'npm'
+
+ - name: Install dependencies
+ run: npm ci
+
+ - name: Generate Prisma Client
+ run: npm run db:generate
+ env:
+ DATABASE_URL: postgresql://postgres:postgres@localhost:5432/test
+
+ - name: Run tests
+ run: npm test
+ env:
+ DATABASE_URL: postgresql://postgres:postgres@localhost:5432/test
+ JWT_SECRET: test-secret-key
+ JWT_REFRESH_SECRET: test-refresh-secret-key
+
build:
runs-on: ubuntu-latest
+ needs: [lint, test]
+
+ steps:
+ - uses: actions/checkout@v3
+
+ - name: Setup Node.js
+ uses: actions/setup-node@v3
+ with:
+ node-version: '18'
+ cache: 'npm'
+
+ - name: Install dependencies
+ run: npm ci
+
+ - name: Build application
+ run: npm run build
+
+ - name: Upload build artifacts
+ uses: actions/upload-artifact@v3
+ with:
+ name: dist
+ path: dist/
+
+ deploy-staging:
+ runs-on: ubuntu-latest
+ needs: [build]
+ if: github.ref == 'refs/heads/develop'
+
+ steps:
+ - uses: actions/checkout@v3
+
+ - name: Download build artifacts
+ uses: actions/download-artifact@v3
+ with:
+ name: dist
+
+ - name: Deploy to staging
+ run: |
+ echo "Deploying to staging environment..."
+ # Add your deployment commands here
+ # Example: scp -r dist/* user@staging-server:/path/to/app
+
+ deploy-production:
+ runs-on: ubuntu-latest
+ needs: [build]
+ if: github.ref == 'refs/heads/main'
+
steps:
- - uses: actions/checkout@v4
- - name: Use Node.js
- uses: actions/setup-node@v4
- with:
- node-version: '18'
- - name: Install dependencies
- run: npm ci
- - name: Lint
- run: npm run lint
- - name: Build
- run: npm run build
- - name: Run tests
- run: npm test --silent
+ - uses: actions/checkout@v3
+
+ - name: Download build artifacts
+ uses: actions/download-artifact@v3
+ with:
+ name: dist
+
+ - name: Deploy to production
+ run: |
+ echo "Deploying to production environment..."
+ # Add your deployment commands here
+ # Example: scp -r dist/* user@production-server:/path/to/app
diff --git a/docs/Email_Templates.md b/docs/Email_Templates.md
new file mode 100644
index 00000000..004b718f
--- /dev/null
+++ b/docs/Email_Templates.md
@@ -0,0 +1,491 @@
+# Email Templates Documentation
+
+This document provides an overview of all email templates used in the PropChain system, including sample payloads, variable references, and preview examples.
+
+## Available Email Templates
+
+### 1. Password Reset Template
+
+**Template File**: `src/email/templates/password-reset.ejs`
+
+**Purpose**: Sent when a user requests a password reset.
+
+**Variables**:
+- `resetUrl` - The password reset link containing the reset token
+
+**Sample Payload**:
+```json
+{
+ "resetUrl": "http://localhost:3000/reset-password?token=abc123xyz789"
+}
+```
+
+**Rendered Preview**:
+```html
+
+
Password Reset Request
+
You have requested to reset your password for your PropChain account.
+
Please click the link below to reset your password:
+
+ Reset Password
+
+
If you didn't request this password reset, please ignore this email.
+
This link will expire in 1 hour for security reasons.
+
Best regards,
The PropChain Team
+
+```
+
+**Usage**:
+```typescript
+await emailService.sendPasswordResetEmail('user@example.com', 'abc123xyz789');
+```
+
+---
+
+### 2. Account Locked Template
+
+**Template File**: `src/email/templates/account-locked.ejs`
+
+**Purpose**: Sent when a user's account is temporarily locked due to multiple failed login attempts.
+
+**Variables**:
+- `lockoutDuration` - Duration of the lockout in minutes
+
+**Sample Payload**:
+```json
+{
+ "lockoutDuration": 30
+}
+```
+
+**Rendered Preview**:
+```html
+
+
Account Locked
+
Your PropChain account has been temporarily locked due to multiple failed login attempts.
+
The lockout will automatically expire in 30 minutes.
+
If you did not attempt to log in, please reset your password immediately or contact our support team.
+
Best regards,
The PropChain Team
+
+```
+
+**Usage**:
+```typescript
+await emailService.sendAccountLockedEmail('user@example.com', 30);
+```
+
+---
+
+### 3. Fraud Alert Template
+
+**Template File**: `src/email/templates/fraud-alert.ejs`
+
+**Purpose**: Sent to administrators when suspicious fraud activity is detected.
+
+**Variables**:
+- `alertId` - Unique identifier for the fraud alert
+- `pattern` - The fraud pattern detected (e.g., EXCESSIVE_FAILED_LOGINS)
+- `severity` - Alert severity (LOW, MEDIUM, HIGH, CRITICAL)
+- `userEmail` - Email of the user associated with the alert
+- `description` - Detailed description of the suspicious activity
+
+**Sample Payload**:
+```json
+{
+ "alertId": "fraud-alert-123",
+ "pattern": "EXCESSIVE_FAILED_LOGINS",
+ "severity": "HIGH",
+ "userEmail": "user@example.com",
+ "description": "The account recorded 10 failed login attempts in the last 30 minutes."
+}
+```
+
+**Rendered Preview**:
+```html
+
+
Fraud Alert Triggered
+
Alert ID: fraud-alert-123
+
Pattern: EXCESSIVE_FAILED_LOGINS
+
Severity: HIGH
+
User: user@example.com
+
Summary: The account recorded 10 failed login attempts in the last 30 minutes.
+
+```
+
+**Usage**:
+```typescript
+await emailService.sendFraudAlertEmail(['admin@example.com'], {
+ alertId: 'fraud-alert-123',
+ pattern: 'EXCESSIVE_FAILED_LOGINS',
+ severity: 'HIGH',
+ title: 'Repeated failed login attempts detected',
+ description: 'The account recorded 10 failed login attempts in the last 30 minutes.',
+ userEmail: 'user@example.com'
+});
+```
+
+---
+
+### 4. Transaction Status Change - Pending
+
+**Template File**: `src/email/templates/transaction-status-pending.ejs`
+
+**Purpose**: Sent when a transaction is created and set to pending status.
+
+**Variables**:
+- `transactionId` - Unique identifier for the transaction
+- `propertyTitle` - Title of the property involved in the transaction
+- `propertyAddress` - Address of the property
+- `buyerName` - Name of the buyer
+- `sellerName` - Name of the seller
+- `amount` - Transaction amount
+- `status` - Current transaction status
+
+**Sample Payload**:
+```json
+{
+ "transactionId": "txn-123",
+ "propertyTitle": "Modern Downtown Apartment",
+ "propertyAddress": "123 Main St, New York, NY 10001",
+ "buyerName": "John Doe",
+ "sellerName": "Jane Smith",
+ "amount": "$500,000",
+ "status": "PENDING"
+}
+```
+
+**Rendered Preview**:
+```html
+
+
Transaction Created
+
Your transaction has been successfully created and is now pending.
+
+
Transaction ID: txn-123
+
Property: Modern Downtown Apartment
+
Address: 123 Main St, New York, NY 10001
+
Amount: $500,000
+
Status: PENDING
+
+
You will receive notifications as the transaction progresses.
+
Best regards,
The PropChain Team
+
+```
+
+---
+
+### 5. Transaction Status Change - Completed
+
+**Template File**: `src/email/templates/transaction-status-completed.ejs`
+
+**Purpose**: Sent when a transaction is successfully completed.
+
+**Variables**:
+- `transactionId` - Unique identifier for the transaction
+- `propertyTitle` - Title of the property involved in the transaction
+- `propertyAddress` - Address of the property
+- `buyerName` - Name of the buyer
+- `sellerName` - Name of the seller
+- `amount` - Transaction amount
+- `completionDate` - Date when transaction was completed
+- `blockchainTxHash` - Blockchain transaction hash (if recorded)
+
+**Sample Payload**:
+```json
+{
+ "transactionId": "txn-123",
+ "propertyTitle": "Modern Downtown Apartment",
+ "propertyAddress": "123 Main St, New York, NY 10001",
+ "buyerName": "John Doe",
+ "sellerName": "Jane Smith",
+ "amount": "$500,000",
+ "completionDate": "January 15, 2026",
+ "blockchainTxHash": "0xabc123...xyz789"
+}
+```
+
+**Rendered Preview**:
+```html
+
+
Transaction Completed
+
Congratulations! Your transaction has been successfully completed.
+
+
Transaction ID: txn-123
+
Property: Modern Downtown Apartment
+
Address: 123 Main St, New York, NY 10001
+
Amount: $500,000
+
Completion Date: January 15, 2026
+
Blockchain Hash: 0xabc123...xyz789
+
+
All documents and records have been updated. Thank you for using PropChain.
+
Best regards,
The PropChain Team
+
+```
+
+---
+
+### 6. Transaction Status Change - Cancelled
+
+**Template File**: `src/email/templates/transaction-status-cancelled.ejs`
+
+**Purpose**: Sent when a transaction is cancelled.
+
+**Variables**:
+- `transactionId` - Unique identifier for the transaction
+- `propertyTitle` - Title of the property involved in the transaction
+- `propertyAddress` - Address of the property
+- `buyerName` - Name of the buyer
+- `sellerName` - Name of the seller
+- `amount` - Transaction amount
+- `cancellationReason` - Reason for cancellation (if provided)
+- `cancelledDate` - Date when transaction was cancelled
+
+**Sample Payload**:
+```json
+{
+ "transactionId": "txn-123",
+ "propertyTitle": "Modern Downtown Apartment",
+ "propertyAddress": "123 Main St, New York, NY 10001",
+ "buyerName": "John Doe",
+ "sellerName": "Jane Smith",
+ "amount": "$500,000",
+ "cancellationReason": "Mutual agreement between parties",
+ "cancelledDate": "January 10, 2026"
+}
+```
+
+**Rendered Preview**:
+```html
+
+
Transaction Cancelled
+
Your transaction has been cancelled.
+
+
Transaction ID: txn-123
+
Property: Modern Downtown Apartment
+
Address: 123 Main St, New York, NY 10001
+
Amount: $500,000
+
Reason: Mutual agreement between parties
+
Cancelled Date: January 10, 2026
+
+
If you have any questions about this cancellation, please contact our support team.
+
Best regards,
The PropChain Team
+
+```
+
+---
+
+## Email Service API
+
+### sendPasswordResetEmail
+
+```typescript
+async sendPasswordResetEmail(email: string, resetToken: string): Promise
+```
+
+Sends a password reset email to the specified user.
+
+**Parameters**:
+- `email` - User's email address
+- `resetToken` - Password reset token
+
+---
+
+### sendAccountLockedEmail
+
+```typescript
+async sendAccountLockedEmail(email: string, lockoutDuration: number): Promise
+```
+
+Sends an account locked notification email.
+
+**Parameters**:
+- `email` - User's email address
+- `lockoutDuration` - Lockout duration in minutes
+
+---
+
+### sendFraudAlertEmail
+
+```typescript
+async sendFraudAlertEmail(recipients: string[], payload: FraudAlertEmailPayload): Promise
+```
+
+Sends fraud alert notifications to administrators.
+
+**Parameters**:
+- `recipients` - Array of admin email addresses
+- `payload` - Fraud alert details
+
+**FraudAlertEmailPayload Interface**:
+```typescript
+interface FraudAlertEmailPayload {
+ alertId: string;
+ pattern: string;
+ severity: string;
+ title: string;
+ description: string;
+ userEmail?: string | null;
+}
+```
+
+---
+
+### sendTransactionStatusEmail
+
+```typescript
+async sendTransactionStatusEmail(
+ email: string,
+ status: TransactionStatus,
+ payload: TransactionStatusPayload
+): Promise
+```
+
+Sends transaction status change notifications.
+
+**Parameters**:
+- `email` - Recipient's email address
+- `status` - Transaction status (PENDING, COMPLETED, CANCELLED)
+- `payload` - Transaction details
+
+**TransactionStatusPayload Interface**:
+```typescript
+interface TransactionStatusPayload {
+ transactionId: string;
+ propertyTitle: string;
+ propertyAddress: string;
+ buyerName: string;
+ sellerName: string;
+ amount: string;
+ completionDate?: string;
+ blockchainTxHash?: string;
+ cancellationReason?: string;
+ cancelledDate?: string;
+}
+```
+
+---
+
+## Template Preview Endpoint
+
+For admin preview purposes, the following endpoint can be used to render email templates with sample data:
+
+### GET /admin/email/preview/:templateName
+
+**Description**: Renders a preview of the specified email template with sample data.
+
+**Authentication**: Required (Admin only)
+
+**Parameters**:
+- `templateName` - Name of the template to preview (e.g., password-reset, account-locked, fraud-alert, transaction-status-pending, transaction-status-completed, transaction-status-cancelled)
+
+**Response**: HTML rendered template
+
+**Example Request**:
+```bash
+GET /admin/email/preview/password-reset
+```
+
+**Example Response**:
+```html
+
+
Password Reset Request
+
You have requested to reset your password for your PropChain account.
+
Please click the link below to reset your password:
+
+ Reset Password
+
+
If you didn't request this password reset, please ignore this email.
+
This link will expire in 1 hour for security reasons.
+
Best regards,
The PropChain Team
+
+```
+
+---
+
+## Testing Email Templates
+
+### Unit Tests
+
+Email templates should be tested with the following test cases:
+
+1. **Variable Rendering**: Ensure all template variables are properly rendered
+2. **Missing Variables**: Test behavior when optional variables are missing
+3. **HTML Validation**: Ensure rendered HTML is valid
+4. **Responsive Design**: Verify templates render correctly on mobile devices
+5. **Link Functionality**: Test that all links in templates are functional
+
+### Test Data
+
+Use the following sample data for testing:
+
+```typescript
+const testEmailData = {
+ passwordReset: {
+ resetUrl: 'http://localhost:3000/reset-password?token=test-token-123'
+ },
+ accountLocked: {
+ lockoutDuration: 30
+ },
+ fraudAlert: {
+ alertId: 'test-alert-123',
+ pattern: 'EXCESSIVE_FAILED_LOGINS',
+ severity: 'HIGH',
+ userEmail: 'test@example.com',
+ description: 'Test fraud alert description'
+ },
+ transactionPending: {
+ transactionId: 'txn-test-123',
+ propertyTitle: 'Test Property',
+ propertyAddress: '123 Test St, Test City, TC 12345',
+ buyerName: 'Test Buyer',
+ sellerName: 'Test Seller',
+ amount: '$100,000',
+ status: 'PENDING'
+ },
+ transactionCompleted: {
+ transactionId: 'txn-test-123',
+ propertyTitle: 'Test Property',
+ propertyAddress: '123 Test St, Test City, TC 12345',
+ buyerName: 'Test Buyer',
+ sellerName: 'Test Seller',
+ amount: '$100,000',
+ completionDate: 'January 15, 2026',
+ blockchainTxHash: '0xabc123...xyz789'
+ },
+ transactionCancelled: {
+ transactionId: 'txn-test-123',
+ propertyTitle: 'Test Property',
+ propertyAddress: '123 Test St, Test City, TC 12345',
+ buyerName: 'Test Buyer',
+ sellerName: 'Test Seller',
+ amount: '$100,000',
+ cancellationReason: 'Test cancellation',
+ cancelledDate: 'January 10, 2026'
+ }
+};
+```
+
+---
+
+## Best Practices
+
+1. **Keep Templates Simple**: Avoid complex logic in templates
+2. **Use Inline CSS**: Email clients have limited CSS support
+3. **Test Across Clients**: Test templates in Gmail, Outlook, Apple Mail, etc.
+4. **Include Plain Text**: Always provide a plain text fallback
+5. **Responsive Design**: Ensure templates work on mobile devices
+6. **Accessibility**: Use semantic HTML and alt text for images
+7. **Personalization**: Use recipient names when possible
+8. **Clear Call-to-Action**: Make action buttons prominent and clear
+9. **Brand Consistency**: Maintain consistent branding across all templates
+10. **Legal Compliance**: Include required footer information (unsubscribe, privacy policy)
+
+---
+
+## Future Enhancements
+
+- Add email template versioning
+- Implement A/B testing for email templates
+- Add support for multi-language templates
+- Create template editor UI for admins
+- Add email analytics tracking
+- Implement template preview with custom data
diff --git a/docs/RBAC_Permission_Matrix.md b/docs/RBAC_Permission_Matrix.md
new file mode 100644
index 00000000..5c672891
--- /dev/null
+++ b/docs/RBAC_Permission_Matrix.md
@@ -0,0 +1,232 @@
+# RBAC Endpoint Permission Matrix
+
+This document provides a comprehensive overview of role-based access control (RBAC) for all protected endpoints in the PropChain backend API.
+
+## User Roles
+
+- **USER**: Default role for registered users. Can manage their own profile, properties, and transactions.
+- **AGENT**: Can manage properties and assist with transactions. Has elevated permissions for property management.
+- **ADMIN**: Full system access including user management, property administration, fraud detection, and system configuration.
+
+## Permission Matrix
+
+### Authentication Endpoints
+
+| Endpoint | Method | USER | AGENT | ADMIN | Description |
+|----------|--------|------|-------|-------|-------------|
+| `/auth/register` | POST | ✅ | ✅ | ✅ | User registration |
+| `/auth/login` | POST | ✅ | ✅ | ✅ | User login |
+| `/auth/refresh` | POST | ✅ | ✅ | ✅ | Refresh access token |
+| `/auth/logout` | POST | ✅ | ✅ | ✅ | User logout |
+| `/auth/password-reset/request` | POST | ✅ | ✅ | ✅ | Request password reset |
+| `/auth/password-reset/reset` | POST | ✅ | ✅ | ✅ | Reset password with token |
+
+### User Management Endpoints
+
+| Endpoint | Method | USER | AGENT | ADMIN | Description |
+|----------|--------|------|-------|-------|-------------|
+| `/users` | POST | ❌ | ❌ | ✅ | Create user (Admin only) |
+| `/users` | GET | ❌ | ❌ | ✅ | List all users (Admin only) |
+| `/users/search` | GET | ❌ | ❌ | ✅ | Search users (Admin only) |
+| `/users/me/statistics` | GET | ✅ | ✅ | ✅ | Get current user statistics |
+| `/users/:id` | GET | ❌ | ❌ | ✅ | Get user by ID (Admin only) |
+| `/users/:id` | PUT | ❌ | ❌ | ✅ | Update user (Admin only) |
+| `/users/:id/block` | POST | ❌ | ❌ | ✅ | Block user (Admin only) |
+| `/users/:id/unblock` | POST | ❌ | ❌ | ✅ | Unblock user (Admin only) |
+| `/users/:id` | DELETE | ❌ | ❌ | ✅ | Delete user (Admin only) |
+| `/users/me/profile` | GET | ✅ | ✅ | ✅ | Get own profile |
+| `/users/me/profile` | PUT | ✅ | ✅ | ✅ | Update own profile |
+| `/users/:id/export` | POST | ✅* | ✅* | ✅ | Export user data (own or admin) |
+| `/users/export/download/:filename` | GET | ✅* | ✅* | ✅ | Download export (own or admin) |
+| `/users/me/deactivate` | POST | ✅ | ✅ | ✅ | Deactivate own account |
+| `/users/:id/verify` | POST | ❌ | ❌ | ✅ | Verify user (Admin only) |
+| `/users/:id/unverify` | POST | ❌ | ❌ | ✅ | Unverify user (Admin only) |
+| `/users/:id/deactivate` | POST | ❌ | ❌ | ✅ | Admin deactivate user |
+| `/users/:id/reactivate` | POST | ❌ | ❌ | ✅ | Reactivate user (Admin only) |
+| `/users/me/preferences` | PUT | ✅ | ✅ | ✅ | Update preferences |
+| `/users/me/referral-stats` | GET | ✅ | ✅ | ✅ | Get referral stats |
+| `/users/me/referrals` | GET | ✅ | ✅ | ✅ | Get own referrals |
+| `/users/me/login-history` | GET | ✅ | ✅ | ✅ | Get login history |
+| `/users/scheduled-deletion` | GET | ❌ | ❌ | ✅ | Get scheduled deletions (Admin only) |
+| `/users/delete-scheduled` | POST | ❌ | ❌ | ✅ | Delete scheduled users (Admin only) |
+
+*USER and AGENT can only access their own data; ADMIN can access any user's data.
+
+### Property Endpoints
+
+| Endpoint | Method | USER | AGENT | ADMIN | Description |
+|----------|--------|------|-------|-------|-------------|
+| `/properties` | POST | ✅ | ✅ | ✅ | Create property |
+| `/properties` | GET | ✅ | ✅ | ✅ | List properties |
+| `/properties/:id` | GET | ✅ | ✅ | ✅ | Get property by ID |
+| `/properties/:id` | PUT | ❌ | ✅ | ✅ | Update property (Agent/Admin only) |
+| `/properties/:id` | DELETE | ❌ | ❌ | ✅ | Delete property (Admin only) |
+| `/properties/:id/approve` | PATCH | ❌ | ❌ | ✅ | Approve property (Admin only) |
+| `/properties/:id/reject` | PATCH | ❌ | ❌ | ✅ | Reject property (Admin only) |
+
+### Transaction Endpoints
+
+| Endpoint | Method | USER | AGENT | ADMIN | Description |
+|----------|--------|------|-------|-------|-------------|
+| `/transactions` | POST | ❌ | ✅ | ✅ | Create transaction (Agent/Admin only) |
+| `/transactions` | GET | ✅ | ✅ | ✅ | List transactions |
+| `/transactions/:id` | GET | ✅ | ✅ | ✅ | Get transaction by ID |
+| `/transactions/:id` | PUT | ❌ | ✅ | ✅ | Update transaction (Agent/Admin only) |
+| `/transactions/:id/record-on-blockchain` | POST | ❌ | ✅ | ✅ | Record on blockchain (Agent/Admin only) |
+| `/transactions/reminders/send` | POST | ❌ | ❌ | ✅ | Send reminders (Admin only) |
+| `/transactions/:id/disputes` | POST | ✅ | ✅ | ✅ | Create dispute |
+| `/transactions/:id/disputes/:id/resolve` | PATCH | ❌ | ❌ | ✅ | Resolve dispute (Admin only) |
+| `/transactions/:id/disputes/:id/status` | PATCH | ❌ | ❌ | ✅ | Update dispute status (Admin only) |
+
+### Document Endpoints
+
+| Endpoint | Method | USER | AGENT | ADMIN | Description |
+|----------|--------|------|-------|-------|-------------|
+| `/documents` | POST | ✅ | ✅ | ✅ | Upload document |
+| `/documents` | GET | ✅ | ✅ | ✅ | List documents |
+| `/documents/:id` | GET | ✅ | ✅ | ✅ | Get document by ID |
+| `/documents/:id` | DELETE | ✅* | ✅* | ✅ | Delete document (own or admin) |
+
+*USER and AGENT can only delete their own documents; ADMIN can delete any.
+
+### Admin Endpoints
+
+| Endpoint | Method | USER | AGENT | ADMIN | Description |
+|----------|--------|------|-------|-------|-------------|
+| `/admin/dashboard` | GET | ❌ | ❌ | ✅ | Get admin dashboard |
+| `/admin/backups` | GET | ❌ | ❌ | ✅ | List backups |
+| `/admin/backups/status` | GET | ❌ | ❌ | ✅ | Get backup status |
+| `/admin/backups/schedule` | GET | ❌ | ❌ | ✅ | Get backup schedule |
+| `/admin/backups/schedule` | PUT | ❌ | ❌ | ✅ | Update backup schedule |
+| `/admin/backups/run` | POST | ❌ | ❌ | ✅ | Run backup |
+| `/admin/backups/:id/restore` | POST | ❌ | ❌ | ✅ | Restore backup |
+| `/admin/backups/:id/download` | GET | ❌ | ❌ | ✅ | Download backup |
+| `/admin/users` | GET | ❌ | ❌ | ✅ | List users (admin view) |
+| `/admin/users/:id` | PATCH | ❌ | ❌ | ✅ | Update user (admin) |
+| `/admin/users/:id/block` | POST | ❌ | ❌ | ✅ | Block user (admin) |
+| `/admin/users/:id/unblock` | POST | ❌ | ❌ | ✅ | Unblock user (admin) |
+| `/admin/properties/moderation/queue` | GET | ❌ | ❌ | ✅ | Get moderation queue |
+| `/admin/properties/:id/approve` | POST | ❌ | ❌ | ✅ | Approve property (admin) |
+| `/admin/properties/:id/reject` | POST | ❌ | ❌ | ✅ | Reject property (admin) |
+| `/admin/properties/:id/flag` | POST | ❌ | ❌ | ✅ | Flag property (admin) |
+| `/admin/properties/moderation/bulk` | POST | ❌ | ❌ | ✅ | Bulk moderate properties |
+| `/admin/transactions/monitoring` | GET | ❌ | ❌ | ✅ | Monitor transactions |
+| `/admin/transactions/monitoring/summary` | GET | ❌ | ❌ | ✅ | Transaction monitoring summary |
+| `/admin/transactions/:id/status` | PATCH | ❌ | ❌ | ✅ | Update transaction status (admin) |
+| `/admin/fraud/alerts` | GET | ❌ | ❌ | ✅ | List fraud alerts |
+| `/admin/fraud/alerts/summary` | GET | ❌ | ❌ | ✅ | Fraud alerts summary |
+| `/admin/fraud/alerts/:id` | GET | ❌ | ❌ | ✅ | Get fraud alert details |
+| `/admin/fraud/alerts/:id` | PATCH | ❌ | ❌ | ✅ | Review fraud alert |
+| `/admin/fraud/alerts/:id/notes` | POST | ❌ | ❌ | ✅ | Add fraud alert note |
+| `/admin/fraud/alerts/:id/block-user` | POST | ❌ | ❌ | ✅ | Block user from fraud alert |
+| `/admin/fraud/users/:id/scan` | POST | ❌ | ❌ | ✅ | Scan user for fraud |
+| `/admin/fraud/properties/:id/scan` | POST | ❌ | ❌ | ✅ | Scan property for fraud |
+
+### Neighborhood Endpoints
+
+| Endpoint | Method | USER | AGENT | ADMIN | Description |
+|----------|--------|------|-------|-------|-------------|
+| `/neighborhoods` | GET | ✅ | ✅ | ✅ | List neighborhoods |
+| `/neighborhoods/:id` | GET | ✅ | ✅ | ✅ | Get neighborhood by ID |
+| `/neighborhoods` | POST | ❌ | ❌ | ✅ | Create neighborhood (Admin only) |
+| `/neighborhoods/:id` | PUT | ❌ | ❌ | ✅ | Update neighborhood (Admin only) |
+| `/neighborhoods/:id` | DELETE | ❌ | ❌ | ✅ | Delete neighborhood (Admin only) |
+| `/neighborhoods/:id/schools` | POST | ❌ | ❌ | ✅ | Add school (Admin only) |
+| `/neighborhoods/:id/schools/:schoolId` | DELETE | ❌ | ❌ | ✅ | Remove school (Admin only) |
+| `/neighborhoods/:id/amenities` | POST | ❌ | ❌ | ✅ | Add amenity (Admin only) |
+| `/neighborhoods/:id/amenities/:amenityId` | DELETE | ❌ | ❌ | ✅ | Remove amenity (Admin only) |
+| `/neighborhoods/property/:propertyId` | PATCH | ❌ | ✅ | ✅ | Link property to neighborhood |
+| `/neighborhoods/property/:propertyId` | DELETE | ❌ | ✅ | ✅ | Unlink property from neighborhood |
+
+### Verification Documents Endpoints
+
+| Endpoint | Method | USER | AGENT | ADMIN | Description |
+|----------|--------|------|-------|-------|-------------|
+| `/users/verification-documents` | POST | ✅ | ✅ | ✅ | Upload verification document |
+| `/users/verification-documents` | GET | ✅ | ✅ | ✅ | List own verification documents |
+| `/admin/verification-documents` | GET | ❌ | ❌ | ✅ | List all verification documents (Admin only) |
+| `/admin/verification-documents/:id/approve` | POST | ❌ | ❌ | ✅ | Approve verification document (Admin only) |
+| `/admin/verification-documents/:id/reject` | POST | ❌ | ❌ | ✅ | Reject verification document (Admin only) |
+
+### User Preferences Endpoints
+
+| Endpoint | Method | USER | AGENT | ADMIN | Description |
+|----------|--------|------|-------|-------|-------------|
+| `/users/preferences` | GET | ✅ | ✅ | ✅ | Get preferences |
+| `/users/preferences` | PUT | ✅ | ✅ | ✅ | Update preferences |
+
+### Activity Log Endpoints
+
+| Endpoint | Method | USER | AGENT | ADMIN | Description |
+|----------|--------|------|-------|-------|-------------|
+| `/users/activity-logs` | GET | ✅ | ✅ | ✅ | Get own activity logs |
+| `/admin/activity-logs` | GET | ❌ | ❌ | ✅ | Get all activity logs (Admin only) |
+
+### Webhooks Endpoints
+
+| Endpoint | Method | USER | AGENT | ADMIN | Description |
+|----------|--------|------|-------|-------|-------------|
+| `/webhooks` | POST | ✅ | ✅ | ✅ | Handle webhook events |
+
+## Implementation Details
+
+### Role Decorator Usage
+
+Protected endpoints use the `@Roles()` decorator in combination with `@UseGuards(JwtAuthGuard, RolesGuard)`:
+
+```typescript
+@UseGuards(JwtAuthGuard, RolesGuard)
+@Roles(UserRole.ADMIN)
+@Get('admin/users')
+getAllUsers() {
+ // Only admins can access
+}
+```
+
+### Multiple Roles
+
+Some endpoints allow multiple roles:
+
+```typescript
+@UseGuards(JwtAuthGuard, RolesGuard)
+@Roles(UserRole.AGENT, UserRole.ADMIN)
+@Post(':id')
+updateProperty() {
+ // Both AGENT and ADMIN can access
+}
+```
+
+### Public Endpoints
+
+Endpoints without `@UseGuards(JwtAuthGuard)` are public and accessible to all users (authenticated and unauthenticated).
+
+### Self-Service vs Admin Access
+
+Some endpoints have conditional access based on ownership:
+- **Self-Service**: Users can access their own data
+- **Admin Access**: Admins can access any user's data
+
+This is typically implemented with additional logic in the controller or service layer.
+
+## Security Best Practices
+
+1. **Always validate roles on the server side** - Never rely on client-side checks
+2. **Use the most restrictive role necessary** - Apply the principle of least privilege
+3. **Audit role changes** - Log all role modifications for security auditing
+4. **Regular permission reviews** - Periodically review and update role permissions
+5. **Document role changes** - Keep this matrix updated when adding new endpoints
+
+## Testing RBAC
+
+When testing RBAC:
+1. Test with each role (USER, AGENT, ADMIN)
+2. Verify unauthorized access returns 403 Forbidden
+3. Verify unauthenticated access returns 401 Unauthorized
+4. Test edge cases (ownership checks, conditional permissions)
+
+## Future Enhancements
+
+- Consider adding more granular permissions (e.g., separate READ and WRITE permissions)
+- Implement resource-level permissions for fine-grained access control
+- Add permission groups for easier management of related permissions
+- Consider adding a SUPER_ADMIN role for system-level operations
diff --git a/src/admin/admin.controller.ts b/src/admin/admin.controller.ts
index 10b13b3c..9aaa2262 100644
--- a/src/admin/admin.controller.ts
+++ b/src/admin/admin.controller.ts
@@ -9,7 +9,8 @@ import {
Query,
Res,
UseGuards,
- UseInterceptors,
+ HttpException,
+ HttpStatus,
} from '@nestjs/common';
import { Response } from 'express';
import { CurrentUser } from '../auth/decorators/current-user.decorator';
@@ -19,7 +20,7 @@ import { RolesGuard } from '../auth/guards/roles.guard';
import { AuthUserPayload } from '../auth/types/auth-user.type';
import { UserRole } from '../types/prisma.types';
import { AdminService } from './admin.service';
-import { AdminAuditInterceptor } from './admin-audit.interceptor';
+import { EmailService } from '../email/email.service';
import {
AddFraudInvestigationNoteDto,
AdminUpdateUserDto,
@@ -40,7 +41,10 @@ import { RestoreBackupDto, UpdateBackupScheduleDto } from '../backup/dto/backup.
@Roles(UserRole.ADMIN)
@UseInterceptors(AdminAuditInterceptor)
export class AdminController {
- constructor(private readonly adminService: AdminService) {}
+ constructor(
+ private readonly adminService: AdminService,
+ private readonly emailService: EmailService,
+ ) {}
@Get('dashboard')
getDashboard() {
@@ -202,4 +206,66 @@ export class AdminController {
scanPropertyForFraud(@Param('id') propertyId: string, @CurrentUser() user: AuthUserPayload) {
return this.adminService.scanPropertyForFraud(propertyId, user.sub);
}
+
+ @Get('email/preview/:templateName')
+ async previewEmailTemplate(@Param('templateName') templateName: string) {
+ const sampleDataMap: Record = {
+ 'password-reset': {
+ resetUrl: 'http://localhost:3000/reset-password?token=sample-token-123',
+ },
+ 'account-locked': {
+ lockoutDuration: 30,
+ },
+ 'fraud-alert': {
+ alertId: 'fraud-alert-123',
+ pattern: 'EXCESSIVE_FAILED_LOGINS',
+ severity: 'HIGH',
+ userEmail: 'user@example.com',
+ description: 'The account recorded 10 failed login attempts in the last 30 minutes.',
+ },
+ 'transaction-status-pending': {
+ transactionId: 'txn-123',
+ propertyTitle: 'Modern Downtown Apartment',
+ propertyAddress: '123 Main St, New York, NY 10001',
+ buyerName: 'John Doe',
+ sellerName: 'Jane Smith',
+ amount: '$500,000',
+ status: 'PENDING',
+ },
+ 'transaction-status-completed': {
+ transactionId: 'txn-123',
+ propertyTitle: 'Modern Downtown Apartment',
+ propertyAddress: '123 Main St, New York, NY 10001',
+ buyerName: 'John Doe',
+ sellerName: 'Jane Smith',
+ amount: '$500,000',
+ completionDate: 'January 15, 2026',
+ blockchainTxHash: '0xabc123...xyz789',
+ },
+ 'transaction-status-cancelled': {
+ transactionId: 'txn-123',
+ propertyTitle: 'Modern Downtown Apartment',
+ propertyAddress: '123 Main St, New York, NY 10001',
+ buyerName: 'John Doe',
+ sellerName: 'Jane Smith',
+ amount: '$500,000',
+ cancellationReason: 'Mutual agreement between parties',
+ cancelledDate: 'January 10, 2026',
+ },
+ };
+
+ const sampleData = sampleDataMap[templateName];
+ if (!sampleData) {
+ throw new HttpException(
+ `Template '${templateName}' not found. Available templates: ${Object.keys(sampleDataMap).join(', ')}`,
+ HttpStatus.NOT_FOUND,
+ );
+ }
+
+ return {
+ templateName,
+ sampleData,
+ note: 'This is a preview with sample data. Actual emails will use real data.',
+ };
+ }
}
diff --git a/src/email/email.service.ts b/src/email/email.service.ts
index 1bc7a8ef..0f538ee7 100644
--- a/src/email/email.service.ts
+++ b/src/email/email.service.ts
@@ -26,6 +26,19 @@ export interface FraudAlertEmailPayload {
userEmail?: string | null;
}
+export interface TransactionStatusPayload {
+ transactionId: string;
+ propertyTitle: string;
+ propertyAddress: string;
+ buyerName: string;
+ sellerName: string;
+ amount: string;
+ completionDate?: string;
+ blockchainTxHash?: string;
+ cancellationReason?: string;
+ cancelledDate?: string;
+}
+
@Injectable()
export class EmailService {
private readonly logger = new Logger(EmailService.name);
@@ -79,6 +92,32 @@ export class EmailService {
);
}
+ async sendTransactionStatusEmail(
+ email: string,
+ status: string,
+ payload: TransactionStatusPayload,
+ ): Promise {
+ const templateMap: Record = {
+ PENDING: 'transaction-status-pending',
+ COMPLETED: 'transaction-status-completed',
+ CANCELLED: 'transaction-status-cancelled',
+ };
+
+ const template = templateMap[status];
+ if (!template) {
+ this.logger.warn(`No template found for transaction status: ${status}`);
+ return;
+ }
+
+ await this.sendEmail({
+ to: email,
+ subject: `[PropChain] Transaction ${status}`,
+ template,
+ context: payload,
+ text: `Your transaction status has been updated to ${status}. Transaction ID: ${payload.transactionId}`,
+ });
+ }
+
async handleBounce(
email: string,
type: 'HARD' | 'SOFT',
diff --git a/src/email/templates/transaction-status-cancelled.ejs b/src/email/templates/transaction-status-cancelled.ejs
new file mode 100644
index 00000000..5ac50246
--- /dev/null
+++ b/src/email/templates/transaction-status-cancelled.ejs
@@ -0,0 +1,16 @@
+
+
Transaction Cancelled
+
Your transaction has been cancelled.
+
+
Transaction ID: <%= transactionId %>
+
Property: <%= propertyTitle %>
+
Address: <%= propertyAddress %>
+
Amount: <%= amount %>
+ <% if (cancellationReason) { %>
+
Reason: <%= cancellationReason %>
+ <% } %>
+
Cancelled Date: <%= cancelledDate %>
+
+
If you have any questions about this cancellation, please contact our support team.
+
Best regards,
The PropChain Team
+
diff --git a/src/email/templates/transaction-status-completed.ejs b/src/email/templates/transaction-status-completed.ejs
new file mode 100644
index 00000000..b98f2d8d
--- /dev/null
+++ b/src/email/templates/transaction-status-completed.ejs
@@ -0,0 +1,16 @@
+
+
Transaction Completed
+
Congratulations! Your transaction has been successfully completed.
+
+
Transaction ID: <%= transactionId %>
+
Property: <%= propertyTitle %>
+
Address: <%= propertyAddress %>
+
Amount: <%= amount %>
+
Completion Date: <%= completionDate %>
+ <% if (blockchainTxHash) { %>
+
Blockchain Hash: <%= blockchainTxHash %>
+ <% } %>
+
+
All documents and records have been updated. Thank you for using PropChain.
+
Best regards,
The PropChain Team
+
diff --git a/src/email/templates/transaction-status-pending.ejs b/src/email/templates/transaction-status-pending.ejs
new file mode 100644
index 00000000..edcaa4b5
--- /dev/null
+++ b/src/email/templates/transaction-status-pending.ejs
@@ -0,0 +1,13 @@
+
+
Transaction Created
+
Your transaction has been successfully created and is now pending.
+
+
Transaction ID: <%= transactionId %>
+
Property: <%= propertyTitle %>
+
Address: <%= propertyAddress %>
+
Amount: <%= amount %>
+
Status: <%= status %>
+
+
You will receive notifications as the transaction progresses.
+
Best regards,
The PropChain Team
+
diff --git a/src/fraud/fraud.service.ts b/src/fraud/fraud.service.ts
index 96144b5d..2148a2ec 100644
--- a/src/fraud/fraud.service.ts
+++ b/src/fraud/fraud.service.ts
@@ -666,13 +666,12 @@ export class FraudService {
const now = new Date();
if (existingAlert) {
+ const severityIncreased =
+ this.severityRank(payload.severity) > this.severityRank(existingAlert.severity);
const updated = await this.prisma.fraudAlert.update({
where: { id: existingAlert.id },
data: {
- severity:
- this.severityRank(payload.severity) > this.severityRank(existingAlert.severity)
- ? payload.severity
- : existingAlert.severity,
+ severity: severityIncreased ? payload.severity : existingAlert.severity,
score: Math.max(existingAlert.score, payload.score),
title: payload.title,
description: payload.description,
@@ -685,6 +684,12 @@ export class FraudService {
},
});
+ // Send notification only if severity increased or occurrence count is a multiple of 5
+ // This prevents spamming admins with repeated low-severity alerts
+ if (severityIncreased || updated.occurrenceCount % 5 === 0) {
+ await this.notifySecurityTeam(updated, true);
+ }
+
if (
payload.autoBlockUser &&
payload.userId &&
@@ -746,7 +751,7 @@ export class FraudService {
});
}
- await this.notifySecurityTeam(created);
+ await this.notifySecurityTeam(created, false);
if (payload.autoBlockUser && payload.userId) {
await this.blockUserForFraud(
@@ -920,18 +925,24 @@ export class FraudService {
});
}
- private async notifySecurityTeam(alert: any) {
+ private async notifySecurityTeam(alert: any, isUpdate: boolean = false) {
if (this.fraudAlertRecipients.length === 0) {
return;
}
try {
+ const title = isUpdate
+ ? `[Fraud Alert Update][${alert.severity}] ${alert.title} (Occurrence #${alert.occurrenceCount})`
+ : `[Fraud Alert][${alert.severity}] ${alert.title}`;
+
await this.emailService.sendFraudAlertEmail(this.fraudAlertRecipients, {
alertId: alert.id,
pattern: alert.pattern,
severity: alert.severity,
- title: alert.title,
- description: alert.description,
+ title,
+ description: isUpdate
+ ? `${alert.description} This alert has been updated due to recurring activity.`
+ : alert.description,
userEmail: alert.user?.email,
});
} catch (error) {
diff --git a/src/notifications/notifications.service.ts b/src/notifications/notifications.service.ts
index fa1fa11a..8be60120 100644
--- a/src/notifications/notifications.service.ts
+++ b/src/notifications/notifications.service.ts
@@ -53,19 +53,24 @@ export class NotificationsService {
});
}
- // 2. Email Notification
+ // 2. Email Notification with template
const canEmail = await this.userPreferencesService.shouldDeliverNotification(
user.id,
'TRANSACTION_UPDATE',
'email',
);
if (canEmail) {
- await this.emailService.sendEmail({
- to: user.email,
- subject: `[PropChain] ${title}`,
- html: `${message}
View your dashboard for details.
`,
- userId: user.id,
- emailType: 'TRANSACTION_UPDATE',
+ await this.emailService.sendTransactionStatusEmail(user.email, transaction.status, {
+ transactionId: transaction.id,
+ propertyTitle: transaction.property.title,
+ propertyAddress: `${transaction.property.address}, ${transaction.property.city}, ${transaction.property.state} ${transaction.property.zipCode}`,
+ buyerName: transaction.buyer.firstName ? `${transaction.buyer.firstName} ${transaction.buyer.lastName || ''}` : transaction.buyer.email,
+ sellerName: transaction.seller.firstName ? `${transaction.seller.firstName} ${transaction.seller.lastName || ''}` : transaction.seller.email,
+ amount: `$${Number(transaction.amount || 0).toLocaleString()}`,
+ completionDate: transaction.status === 'COMPLETED' ? new Date().toLocaleDateString() : undefined,
+ blockchainTxHash: transaction.blockchainTxHash || undefined,
+ cancellationReason: transaction.cancellationReason || undefined,
+ cancelledDate: transaction.status === 'CANCELLED' ? new Date().toLocaleDateString() : undefined,
});
}