Add v120g 212ss3
This commit is contained in:
parent
156d713b20
commit
92ec130117
@ -25,7 +25,7 @@ yarn-debug.log*
|
|||||||
yarn-error.log*
|
yarn-error.log*
|
||||||
|
|
||||||
# local env files
|
# local env files
|
||||||
#.env*.local
|
.env*.local
|
||||||
|
|
||||||
# vercel
|
# vercel
|
||||||
.vercel
|
.vercel
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
|
MONGO_INITDB_ROOT_USERNAME=root
|
||||||
MONGO_INITDB_ROOT_USERNAME=your_username
|
MONGO_INITDB_ROOT_PASSWORD=mongodb_str_p%40ss
|
||||||
MONGO_INITDB_ROOT_PASSWORD=your_secure_password
|
|
||||||
MONGO_INITDB_DATABASE=Infinity
|
MONGO_INITDB_DATABASE=Infinity
|
||||||
# NEXT_PUBLIC_API_URL=https://irongym.yznapps.com:3000
|
DB_URI=mongodb://${MONGO_INITDB_ROOT_USERNAME}:${MONGO_INITDB_ROOT_PASSWORD}@localhost:27017/Infinity?authSource=admin
|
||||||
NEXT_PUBLIC_API_BASE=http://localhost:3000
|
NEXT_PUBLIC_API_BASE=http://localhost:3000
|
||||||
NODE_ENV=production
|
|
||||||
DB_URI=mongodb://${MONGO_INITDB_ROOT_USERNAME}:${MONGO_INITDB_ROOT_PASSWORD}@mongodb:27017/Infinity?authSource=admin
|
# ADMIN_USERNAME=admin
|
||||||
|
# ADMIN_PASSWORD=your_secure_admin_password
|
||||||
155
webapp/API_DOCUMENTATION.md
Normal file
155
webapp/API_DOCUMENTATION.md
Normal file
@ -0,0 +1,155 @@
|
|||||||
|
# Member Lookup API Documentation
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
This API endpoint allows secure lookup of member information using encrypted member IDs. It's designed to be used with an Expo Android app that scans QR codes containing encrypted member IDs.
|
||||||
|
|
||||||
|
## Endpoint
|
||||||
|
```
|
||||||
|
GET /api/member-lookup?encryptedId={encrypted_member_id}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Security
|
||||||
|
- Member IDs are encrypted using AES-256-CBC encryption
|
||||||
|
- QR codes contain encrypted member IDs, not plain text IDs
|
||||||
|
- The API validates and decrypts the member ID before database lookup
|
||||||
|
|
||||||
|
## Request Parameters
|
||||||
|
|
||||||
|
| Parameter | Type | Required | Description |
|
||||||
|
|-----------|------|----------|-------------|
|
||||||
|
| encryptedId | string | Yes | The encrypted member ID obtained from QR code |
|
||||||
|
|
||||||
|
## Response Format
|
||||||
|
|
||||||
|
### Success Response (200)
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"message": "Member information retrieved successfully",
|
||||||
|
"data": {
|
||||||
|
"name": "John Doe",
|
||||||
|
"gender": "m",
|
||||||
|
"planDelay": 1,
|
||||||
|
"planStart": "Mon, 01 Jan 2024 00:00:00 GMT",
|
||||||
|
"planStatus": "active",
|
||||||
|
"planExpAt": "Thu, 01 Feb 2024 00:00:00 GMT"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Error Responses
|
||||||
|
|
||||||
|
#### Missing Parameter (400)
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": false,
|
||||||
|
"message": "Missing encrypted member ID"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Invalid Encryption (400)
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": false,
|
||||||
|
"message": "Invalid encrypted member ID"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Member Not Found (404)
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": false,
|
||||||
|
"message": "Member not found"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Server Error (500)
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": false,
|
||||||
|
"message": "Server error"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Response Data Fields
|
||||||
|
|
||||||
|
| Field | Type | Description |
|
||||||
|
|-------|------|-------------|
|
||||||
|
| name | string | Full name (firstName + lastName) |
|
||||||
|
| gender | string | Gender ("m" for male, "f" for female) |
|
||||||
|
| planDelay | number | Plan duration in months |
|
||||||
|
| planStart | string | Plan start date (UTC string) |
|
||||||
|
| planStatus | string | "active" or "expired" |
|
||||||
|
| planExpAt | string | Plan expiration date (UTC string) |
|
||||||
|
|
||||||
|
## Usage with Android App
|
||||||
|
|
||||||
|
### 1. QR Code Scanning
|
||||||
|
- Scan the QR code from the member details popup
|
||||||
|
- Extract the encrypted member ID from the QR code data
|
||||||
|
|
||||||
|
### 2. API Call
|
||||||
|
```javascript
|
||||||
|
// Example using fetch in React Native/Expo
|
||||||
|
const lookupMember = async (encryptedId) => {
|
||||||
|
try {
|
||||||
|
const response = await fetch(
|
||||||
|
`https://your-domain.com/api/member-lookup?encryptedId=${encodeURIComponent(encryptedId)}`
|
||||||
|
);
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (data.success) {
|
||||||
|
// Display member information in card view
|
||||||
|
return data.data;
|
||||||
|
} else {
|
||||||
|
// Handle error
|
||||||
|
console.error('API Error:', data.message);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Network Error:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Display in Card View
|
||||||
|
Use the returned data to populate your Android card view with:
|
||||||
|
- Member name
|
||||||
|
- Gender
|
||||||
|
- Plan information (duration, start date, expiration)
|
||||||
|
- Plan status (active/expired with appropriate styling)
|
||||||
|
|
||||||
|
## Security Considerations
|
||||||
|
|
||||||
|
1. **Encryption Key**: Ensure the `ENCRYPTION_KEY` environment variable is set to a secure 32-character string in production
|
||||||
|
2. **HTTPS**: Always use HTTPS in production to protect data in transit
|
||||||
|
3. **Rate Limiting**: Consider implementing rate limiting to prevent abuse
|
||||||
|
4. **Authentication**: For additional security, consider adding API authentication
|
||||||
|
|
||||||
|
## Environment Variables
|
||||||
|
|
||||||
|
```env
|
||||||
|
ENCRYPTION_KEY=your-32-character-secret-key-here!
|
||||||
|
```
|
||||||
|
|
||||||
|
## Files Modified/Created
|
||||||
|
|
||||||
|
1. **Created**: `src/utils/encryption.ts` - Encryption/decryption utilities
|
||||||
|
2. **Created**: `src/app/api/member-lookup/route.ts` - API endpoint
|
||||||
|
3. **Modified**: `src/components/dashboard/members/parts/detailsPopUp.tsx` - QR code generation with encryption
|
||||||
|
4. **Modified**: `src/messages/en.json` - English translations
|
||||||
|
5. **Modified**: `src/messages/ar.json` - Arabic translations
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
1. Start the development server: `npm run dev`
|
||||||
|
2. Open a member details popup to generate a QR code
|
||||||
|
3. The QR code now contains an encrypted member ID
|
||||||
|
4. Test the API endpoint with the encrypted ID
|
||||||
|
|
||||||
|
## Example QR Code Flow
|
||||||
|
|
||||||
|
1. **Web App**: Generates QR code with encrypted member ID
|
||||||
|
2. **Android App**: Scans QR code and extracts encrypted ID
|
||||||
|
3. **Android App**: Calls `/api/member-lookup?encryptedId=...`
|
||||||
|
4. **API**: Decrypts ID, looks up member, returns information
|
||||||
|
5. **Android App**: Displays member information in card view
|
||||||
@ -1,6 +1,5 @@
|
|||||||
/** @type {import('next').NextConfig} */
|
/** @type {import('next').NextConfig} */
|
||||||
const nextConfig = {
|
const nextConfig = {
|
||||||
output: 'standalone',
|
|
||||||
typescript: {
|
typescript: {
|
||||||
ignoreBuildErrors: true,
|
ignoreBuildErrors: true,
|
||||||
},
|
},
|
||||||
|
|||||||
218
webapp/package-lock.json
generated
218
webapp/package-lock.json
generated
@ -23,6 +23,7 @@
|
|||||||
"@tippyjs/react": "^4.2.6",
|
"@tippyjs/react": "^4.2.6",
|
||||||
"@types/bcrypt": "^5.0.0",
|
"@types/bcrypt": "^5.0.0",
|
||||||
"@types/node": "20.4.4",
|
"@types/node": "20.4.4",
|
||||||
|
"@types/qrcode": "^1.5.5",
|
||||||
"@types/react": "^18.2.15",
|
"@types/react": "^18.2.15",
|
||||||
"@types/react-dom": "^18.2.7",
|
"@types/react-dom": "^18.2.7",
|
||||||
"apexcharts": "^3.42.0",
|
"apexcharts": "^3.42.0",
|
||||||
@ -37,6 +38,7 @@
|
|||||||
"next": "^13.4.12",
|
"next": "^13.4.12",
|
||||||
"next-intl": "^2.19.0",
|
"next-intl": "^2.19.0",
|
||||||
"postcss": "8.4.27",
|
"postcss": "8.4.27",
|
||||||
|
"qrcode": "^1.5.4",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-animate-height": "^3.2.2",
|
"react-animate-height": "^3.2.2",
|
||||||
"react-apexcharts": "^1.4.1",
|
"react-apexcharts": "^1.4.1",
|
||||||
@ -1616,6 +1618,15 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz",
|
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz",
|
||||||
"integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w=="
|
"integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w=="
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/qrcode": {
|
||||||
|
"version": "1.5.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/qrcode/-/qrcode-1.5.5.tgz",
|
||||||
|
"integrity": "sha512-CdfBi/e3Qk+3Z/fXYShipBT13OJ2fDO2Q2w5CIP5anLTLIndQG9z6P1cnm+8zCWSpm5dnxMFd/uREtb0EXuQzg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/quill": {
|
"node_modules/@types/quill": {
|
||||||
"version": "1.3.10",
|
"version": "1.3.10",
|
||||||
"resolved": "https://registry.npmjs.org/@types/quill/-/quill-1.3.10.tgz",
|
"resolved": "https://registry.npmjs.org/@types/quill/-/quill-1.3.10.tgz",
|
||||||
@ -2307,6 +2318,15 @@
|
|||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/camelcase": {
|
||||||
|
"version": "5.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
|
||||||
|
"integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/camelcase-css": {
|
"node_modules/camelcase-css": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz",
|
||||||
@ -2407,6 +2427,17 @@
|
|||||||
"resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz",
|
||||||
"integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA=="
|
"integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA=="
|
||||||
},
|
},
|
||||||
|
"node_modules/cliui": {
|
||||||
|
"version": "6.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz",
|
||||||
|
"integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"string-width": "^4.2.0",
|
||||||
|
"strip-ansi": "^6.0.0",
|
||||||
|
"wrap-ansi": "^6.2.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/clone": {
|
"node_modules/clone": {
|
||||||
"version": "2.1.2",
|
"version": "2.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz",
|
||||||
@ -2580,6 +2611,15 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/decamelize": {
|
||||||
|
"version": "1.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
|
||||||
|
"integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/deep-equal": {
|
"node_modules/deep-equal": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.1.1.tgz",
|
||||||
@ -2715,6 +2755,12 @@
|
|||||||
"resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz",
|
||||||
"integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw=="
|
"integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw=="
|
||||||
},
|
},
|
||||||
|
"node_modules/dijkstrajs": {
|
||||||
|
"version": "1.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/dijkstrajs/-/dijkstrajs-1.0.3.tgz",
|
||||||
|
"integrity": "sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/dir-glob": {
|
"node_modules/dir-glob": {
|
||||||
"version": "3.0.1",
|
"version": "3.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
|
||||||
@ -3680,6 +3726,15 @@
|
|||||||
"node": ">=6.9.0"
|
"node": ">=6.9.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/get-caller-file": {
|
||||||
|
"version": "2.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
|
||||||
|
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
|
||||||
|
"license": "ISC",
|
||||||
|
"engines": {
|
||||||
|
"node": "6.* || 8.* || >= 10.*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/get-intrinsic": {
|
"node_modules/get-intrinsic": {
|
||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz",
|
||||||
@ -5366,6 +5421,15 @@
|
|||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/p-try": {
|
||||||
|
"version": "2.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
|
||||||
|
"integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/parchment": {
|
"node_modules/parchment": {
|
||||||
"version": "1.1.4",
|
"version": "1.1.4",
|
||||||
"resolved": "https://registry.npmjs.org/parchment/-/parchment-1.1.4.tgz",
|
"resolved": "https://registry.npmjs.org/parchment/-/parchment-1.1.4.tgz",
|
||||||
@ -5473,6 +5537,15 @@
|
|||||||
"node": ">= 6"
|
"node": ">= 6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/pngjs": {
|
||||||
|
"version": "5.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/pngjs/-/pngjs-5.0.0.tgz",
|
||||||
|
"integrity": "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10.13.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/postcss": {
|
"node_modules/postcss": {
|
||||||
"version": "8.4.27",
|
"version": "8.4.27",
|
||||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.27.tgz",
|
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.27.tgz",
|
||||||
@ -5633,6 +5706,23 @@
|
|||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/qrcode": {
|
||||||
|
"version": "1.5.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/qrcode/-/qrcode-1.5.4.tgz",
|
||||||
|
"integrity": "sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"dijkstrajs": "^1.0.1",
|
||||||
|
"pngjs": "^5.0.0",
|
||||||
|
"yargs": "^15.3.1"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"qrcode": "bin/qrcode"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10.13.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/queue-microtask": {
|
"node_modules/queue-microtask": {
|
||||||
"version": "1.2.3",
|
"version": "1.2.3",
|
||||||
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
|
||||||
@ -5955,6 +6045,21 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/require-directory": {
|
||||||
|
"version": "2.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
|
||||||
|
"integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/require-main-filename": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==",
|
||||||
|
"license": "ISC"
|
||||||
|
},
|
||||||
"node_modules/reselect": {
|
"node_modules/reselect": {
|
||||||
"version": "4.1.8",
|
"version": "4.1.8",
|
||||||
"resolved": "https://registry.npmjs.org/reselect/-/reselect-4.1.8.tgz",
|
"resolved": "https://registry.npmjs.org/reselect/-/reselect-4.1.8.tgz",
|
||||||
@ -7207,6 +7312,12 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/which-module": {
|
||||||
|
"version": "2.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz",
|
||||||
|
"integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==",
|
||||||
|
"license": "ISC"
|
||||||
|
},
|
||||||
"node_modules/which-typed-array": {
|
"node_modules/which-typed-array": {
|
||||||
"version": "1.1.11",
|
"version": "1.1.11",
|
||||||
"resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.11.tgz",
|
"resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.11.tgz",
|
||||||
@ -7233,11 +7344,31 @@
|
|||||||
"string-width": "^1.0.2 || 2 || 3 || 4"
|
"string-width": "^1.0.2 || 2 || 3 || 4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/wrap-ansi": {
|
||||||
|
"version": "6.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz",
|
||||||
|
"integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"ansi-styles": "^4.0.0",
|
||||||
|
"string-width": "^4.1.0",
|
||||||
|
"strip-ansi": "^6.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/wrappy": {
|
"node_modules/wrappy": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||||
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
|
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
|
||||||
},
|
},
|
||||||
|
"node_modules/y18n": {
|
||||||
|
"version": "4.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz",
|
||||||
|
"integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==",
|
||||||
|
"license": "ISC"
|
||||||
|
},
|
||||||
"node_modules/yallist": {
|
"node_modules/yallist": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
|
||||||
@ -7251,6 +7382,93 @@
|
|||||||
"node": ">= 14"
|
"node": ">= 14"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/yargs": {
|
||||||
|
"version": "15.4.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz",
|
||||||
|
"integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"cliui": "^6.0.0",
|
||||||
|
"decamelize": "^1.2.0",
|
||||||
|
"find-up": "^4.1.0",
|
||||||
|
"get-caller-file": "^2.0.1",
|
||||||
|
"require-directory": "^2.1.1",
|
||||||
|
"require-main-filename": "^2.0.0",
|
||||||
|
"set-blocking": "^2.0.0",
|
||||||
|
"string-width": "^4.2.0",
|
||||||
|
"which-module": "^2.0.0",
|
||||||
|
"y18n": "^4.0.0",
|
||||||
|
"yargs-parser": "^18.1.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/yargs-parser": {
|
||||||
|
"version": "18.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz",
|
||||||
|
"integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"camelcase": "^5.0.0",
|
||||||
|
"decamelize": "^1.2.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/yargs/node_modules/find-up": {
|
||||||
|
"version": "4.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
|
||||||
|
"integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"locate-path": "^5.0.0",
|
||||||
|
"path-exists": "^4.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/yargs/node_modules/locate-path": {
|
||||||
|
"version": "5.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
|
||||||
|
"integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"p-locate": "^4.1.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/yargs/node_modules/p-limit": {
|
||||||
|
"version": "2.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
|
||||||
|
"integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"p-try": "^2.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/yargs/node_modules/p-locate": {
|
||||||
|
"version": "4.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
|
||||||
|
"integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"p-limit": "^2.2.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/yocto-queue": {
|
"node_modules/yocto-queue": {
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
|
||||||
|
|||||||
@ -24,6 +24,7 @@
|
|||||||
"@tippyjs/react": "^4.2.6",
|
"@tippyjs/react": "^4.2.6",
|
||||||
"@types/bcrypt": "^5.0.0",
|
"@types/bcrypt": "^5.0.0",
|
||||||
"@types/node": "20.4.4",
|
"@types/node": "20.4.4",
|
||||||
|
"@types/qrcode": "^1.5.5",
|
||||||
"@types/react": "^18.2.15",
|
"@types/react": "^18.2.15",
|
||||||
"@types/react-dom": "^18.2.7",
|
"@types/react-dom": "^18.2.7",
|
||||||
"apexcharts": "^3.42.0",
|
"apexcharts": "^3.42.0",
|
||||||
@ -38,6 +39,7 @@
|
|||||||
"next": "^13.4.12",
|
"next": "^13.4.12",
|
||||||
"next-intl": "^2.19.0",
|
"next-intl": "^2.19.0",
|
||||||
"postcss": "8.4.27",
|
"postcss": "8.4.27",
|
||||||
|
"qrcode": "^1.5.4",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-animate-height": "^3.2.2",
|
"react-animate-height": "^3.2.2",
|
||||||
"react-apexcharts": "^1.4.1",
|
"react-apexcharts": "^1.4.1",
|
||||||
|
|||||||
@ -152,7 +152,7 @@ async function updateMembersOverviewStatistics() {
|
|||||||
registerAt_unix: { $lt: endOfDayUnix }
|
registerAt_unix: { $lt: endOfDayUnix }
|
||||||
});
|
});
|
||||||
|
|
||||||
daysInWeek[weekDaysOrder[i]] = {
|
(daysInWeek as {[key: string]: any})[weekDaysOrder[i]] = {
|
||||||
totalMembers,
|
totalMembers,
|
||||||
totalActiveSubs,
|
totalActiveSubs,
|
||||||
totalUnActiveSubs,
|
totalUnActiveSubs,
|
||||||
@ -253,7 +253,7 @@ async function updateMembersOverviewStatistics() {
|
|||||||
registerAt_unix: { $lt: endOfDayUnix }
|
registerAt_unix: { $lt: endOfDayUnix }
|
||||||
});
|
});
|
||||||
|
|
||||||
daysInMonthlyWeekForYear[weekDaysOrder[i]] = {
|
(daysInMonthlyWeekForYear as {[key: string]: any})[weekDaysOrder[i]] = {
|
||||||
totalMembers,
|
totalMembers,
|
||||||
totalActiveSubs,
|
totalActiveSubs,
|
||||||
totalUnActiveSubs,
|
totalUnActiveSubs,
|
||||||
|
|||||||
@ -10,15 +10,17 @@ import useMediaQuery from '@mui/material/useMediaQuery';
|
|||||||
import Slide from '@mui/material/Slide';
|
import Slide from '@mui/material/Slide';
|
||||||
import { TransitionProps } from '@mui/material/transitions';
|
import { TransitionProps } from '@mui/material/transitions';
|
||||||
import { Formik } from 'formik';
|
import { Formik } from 'formik';
|
||||||
|
import { useRef } from 'react';
|
||||||
import DialogContent from '@mui/material/DialogContent';
|
import DialogContent from '@mui/material/DialogContent';
|
||||||
import DialogContentText from '@mui/material/DialogContentText';
|
import DialogContentText from '@mui/material/DialogContentText';
|
||||||
import DialogActions from '@mui/material/DialogActions';
|
import DialogActions from '@mui/material/DialogActions';
|
||||||
|
import { encryptMemberId } from '@/utils/encryption';
|
||||||
|
|
||||||
export default function ServiceDetailsPopUp()
|
export default function ServiceDetailsPopUp()
|
||||||
{
|
{
|
||||||
// declare the needed variables
|
// declare the needed variables
|
||||||
// redux
|
// redux
|
||||||
const dispatch = useDispatch<AppDispatch>();
|
const dispatch = useDispatch();
|
||||||
// intl-18
|
// intl-18
|
||||||
const t = useTranslations('members');
|
const t = useTranslations('members');
|
||||||
// mui
|
// mui
|
||||||
@ -43,6 +45,31 @@ export default function ServiceDetailsPopUp()
|
|||||||
data: null
|
data: null
|
||||||
}));
|
}));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// QR Code generation function using QR Server API with encrypted member ID
|
||||||
|
const generateQRCode = (memberId: string) => {
|
||||||
|
const encryptedId = encryptMemberId(memberId);
|
||||||
|
return `https://api.qrserver.com/v1/create-qr-code/?size=360x360&data=${encodeURIComponent(encryptedId)}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Download QR code function
|
||||||
|
const downloadQRCode = async (memberId: string) => {
|
||||||
|
try {
|
||||||
|
const qrUrl = generateQRCode(memberId);
|
||||||
|
const response = await fetch(qrUrl);
|
||||||
|
const blob = await response.blob();
|
||||||
|
const url = window.URL.createObjectURL(blob);
|
||||||
|
const link = document.createElement('a');
|
||||||
|
link.href = url;
|
||||||
|
link.download = `member-${memberId}-qr.png`;
|
||||||
|
document.body.appendChild(link);
|
||||||
|
link.click();
|
||||||
|
document.body.removeChild(link);
|
||||||
|
window.URL.revokeObjectURL(url);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error downloading QR code:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
// we dont mount the pop up if we dont need it
|
// we dont mount the pop up if we dont need it
|
||||||
if(!detailsPopUp) return <></>
|
if(!detailsPopUp) return <></>
|
||||||
// convert unix into read able date
|
// convert unix into read able date
|
||||||
@ -238,7 +265,56 @@ export default function ServiceDetailsPopUp()
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="w-full flex text-start mt-7">
|
<div className="w-full flex text-start mt-7">
|
||||||
<p>{t('registerAt') + ' ' + UnixToReadAbleDate(parseInt(detailsPopUpData?.registerAt_unix))}</p>
|
<p>
|
||||||
|
{t('registerAt') + ' ' + '[ ' +
|
||||||
|
new Date(detailsPopUpData?.registerAt || '').toLocaleDateString(
|
||||||
|
t('locale') === 'ar' ? 'ar-EG' : 'en-US',
|
||||||
|
{
|
||||||
|
weekday: 'long',
|
||||||
|
day: 'numeric',
|
||||||
|
month: 'numeric',
|
||||||
|
year: 'numeric',
|
||||||
|
calendar: t('locale') === 'ar' ? 'gregory' : undefined
|
||||||
|
}
|
||||||
|
)
|
||||||
|
+ ' ] '}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* QR Code Section */}
|
||||||
|
<div className="w-full flex flex-col gap-3 mt-7 border-t pt-7">
|
||||||
|
<h3 className="font-bold">{t('memberQRCode') || 'Member QR Code'}</h3>
|
||||||
|
<div className="flex lg:flex-row flex-col lg:items-center gap-4">
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
<p className="text-sm text-gray-600 dark:text-gray-400">
|
||||||
|
{t('qrCodeDescription') || 'Scan this QR code to quickly access member information'}
|
||||||
|
</p>
|
||||||
|
<img
|
||||||
|
src={generateQRCode(detailsPopUpData?._id || '')}
|
||||||
|
alt="Member QR Code"
|
||||||
|
className="w-64 h-64 border-2 border-gray-300 rounded-lg mt-1"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col gap-2 text-white">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => downloadQRCode(detailsPopUpData?._id || '')}
|
||||||
|
className="
|
||||||
|
text-slate-50 bg-slate-500 hover:bg-slate-500/80
|
||||||
|
px-4 py-2 rounded-md font-semibold transition-colors
|
||||||
|
flex items-center gap-2
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M12 16L7 11L8.4 9.6L11 12.2V4H13V12.2L15.6 9.6L17 11L12 16ZM6 20C5.45 20 4.979 19.804 4.587 19.412C4.195 19.02 3.99934 18.5493 4 18V15H6V18H18V15H20V18C20 18.55 19.804 19.021 19.412 19.413C19.02 19.805 18.5493 20.0007 18 20H6Z" fill="currentColor"/>
|
||||||
|
</svg>
|
||||||
|
{t('downloadQR') || 'Download QR Code'}
|
||||||
|
</button>
|
||||||
|
<p className="text-xs text-gray-500">
|
||||||
|
{t('qrDownloadNote') || 'Downloads as PNG image'}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
|
|||||||
@ -140,7 +140,11 @@
|
|||||||
"BodyStateInformations": "معلومات عن حالة الجسم",
|
"BodyStateInformations": "معلومات عن حالة الجسم",
|
||||||
"planDetails": "معلومات عن الاشتراك",
|
"planDetails": "معلومات عن الاشتراك",
|
||||||
"currentWeight": "الوزن الحالي",
|
"currentWeight": "الوزن الحالي",
|
||||||
"currentBodyForm": "حالة الجسم الحالية"
|
"currentBodyForm": "حالة الجسم الحالية",
|
||||||
|
"memberQRCode": "رمز QR للعضو",
|
||||||
|
"qrCodeDescription": "امسح رمز الاستجابة السريعة هذا للوصول السريع إلى معلومات العضو:",
|
||||||
|
"downloadQR": "تحميل رمز QR",
|
||||||
|
"qrDownloadNote": "يتم التحميل كصورة PNG"
|
||||||
},
|
},
|
||||||
"workers": {
|
"workers": {
|
||||||
"workers": "العمال",
|
"workers": "العمال",
|
||||||
|
|||||||
@ -144,7 +144,11 @@
|
|||||||
"BodyStateInformations": "Body state informations",
|
"BodyStateInformations": "Body state informations",
|
||||||
"planDetails": "Plan details",
|
"planDetails": "Plan details",
|
||||||
"currentWeight": "Current weight",
|
"currentWeight": "Current weight",
|
||||||
"currentBodyForm": "Current body form"
|
"currentBodyForm": "Current body form",
|
||||||
|
"memberQRCode": "Member QR Code",
|
||||||
|
"qrCodeDescription": "Scan this QR code to quickly access member information:",
|
||||||
|
"downloadQR": "Download QR Code",
|
||||||
|
"qrDownloadNote": "Downloads as PNG image"
|
||||||
},
|
},
|
||||||
"workers": {
|
"workers": {
|
||||||
"workers": "Workers",
|
"workers": "Workers",
|
||||||
|
|||||||
@ -6,8 +6,6 @@
|
|||||||
|
|
||||||
export default async function validateAuthToken(authToken : string | undefined) : Promise<boolean | undefined>
|
export default async function validateAuthToken(authToken : string | undefined) : Promise<boolean | undefined>
|
||||||
{
|
{
|
||||||
console.log("----------NEXT_PUBLIC_API_BASE : " , process.env.NEXT_PUBLIC_API_BASE)
|
|
||||||
//process.env.NEXT_PUBLIC_API_BASE = "http://localhost:3000"
|
|
||||||
let data : {
|
let data : {
|
||||||
success : boolean,
|
success : boolean,
|
||||||
} = await (await fetch(process.env.NEXT_PUBLIC_API_BASE+"/api/auth?authToken="+authToken)).json()
|
} = await (await fetch(process.env.NEXT_PUBLIC_API_BASE+"/api/auth?authToken="+authToken)).json()
|
||||||
|
|||||||
41
webapp/src/utils/encryption.ts
Normal file
41
webapp/src/utils/encryption.ts
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
/**
|
||||||
|
* @description Utility functions for encrypting and decrypting member IDs
|
||||||
|
*/
|
||||||
|
|
||||||
|
import crypto from 'crypto';
|
||||||
|
|
||||||
|
// Secret key for encryption - in production, this should be in environment variables
|
||||||
|
const ENCRYPTION_KEY = process.env.ENCRYPTION_KEY || 'your-32-character-secret-key-here!';
|
||||||
|
const ALGORITHM = 'aes-256-cbc';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encrypts a member ID
|
||||||
|
* @param memberId - The member ID to encrypt
|
||||||
|
* @returns Encrypted string
|
||||||
|
*/
|
||||||
|
export function encryptMemberId(memberId: string): string {
|
||||||
|
const iv = crypto.randomBytes(16);
|
||||||
|
const cipher = crypto.createCipher(ALGORITHM, ENCRYPTION_KEY);
|
||||||
|
let encrypted = cipher.update(memberId, 'utf8', 'hex');
|
||||||
|
encrypted += cipher.final('hex');
|
||||||
|
return iv.toString('hex') + ':' + encrypted;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decrypts an encrypted member ID
|
||||||
|
* @param encryptedMemberId - The encrypted member ID
|
||||||
|
* @returns Decrypted member ID
|
||||||
|
*/
|
||||||
|
export function decryptMemberId(encryptedMemberId: string): string {
|
||||||
|
try {
|
||||||
|
const textParts = encryptedMemberId.split(':');
|
||||||
|
const iv = Buffer.from(textParts.shift()!, 'hex');
|
||||||
|
const encryptedText = textParts.join(':');
|
||||||
|
const decipher = crypto.createDecipher(ALGORITHM, ENCRYPTION_KEY);
|
||||||
|
let decrypted = decipher.update(encryptedText, 'hex', 'utf8');
|
||||||
|
decrypted += decipher.final('utf8');
|
||||||
|
return decrypted;
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error('Invalid encrypted member ID');
|
||||||
|
}
|
||||||
|
}
|
||||||
64
webapp/test-api.js
Normal file
64
webapp/test-api.js
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
/**
|
||||||
|
* Test script for the member lookup API
|
||||||
|
* This script tests the encryption/decryption and API functionality
|
||||||
|
*/
|
||||||
|
|
||||||
|
const crypto = require('crypto');
|
||||||
|
|
||||||
|
// Same encryption configuration as in the utils
|
||||||
|
const ENCRYPTION_KEY = process.env.ENCRYPTION_KEY || 'your-32-character-secret-key-here!';
|
||||||
|
const ALGORITHM = 'aes-256-cbc';
|
||||||
|
|
||||||
|
// Encryption function (same as in utils/encryption.ts)
|
||||||
|
function encryptMemberId(memberId) {
|
||||||
|
const iv = crypto.randomBytes(16);
|
||||||
|
const cipher = crypto.createCipher(ALGORITHM, ENCRYPTION_KEY);
|
||||||
|
let encrypted = cipher.update(memberId, 'utf8', 'hex');
|
||||||
|
encrypted += cipher.final('hex');
|
||||||
|
return iv.toString('hex') + ':' + encrypted;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decryption function (same as in utils/encryption.ts)
|
||||||
|
function decryptMemberId(encryptedMemberId) {
|
||||||
|
try {
|
||||||
|
const textParts = encryptedMemberId.split(':');
|
||||||
|
const iv = Buffer.from(textParts.shift(), 'hex');
|
||||||
|
const encryptedText = textParts.join(':');
|
||||||
|
const decipher = crypto.createDecipher(ALGORITHM, ENCRYPTION_KEY);
|
||||||
|
let decrypted = decipher.update(encryptedText, 'hex', 'utf8');
|
||||||
|
decrypted += decipher.final('utf8');
|
||||||
|
return decrypted;
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error('Invalid encrypted member ID');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test the encryption/decryption
|
||||||
|
const testMemberId = '507f1f77bcf86cd799439011'; // Example MongoDB ObjectId
|
||||||
|
console.log('Original Member ID:', testMemberId);
|
||||||
|
|
||||||
|
const encrypted = encryptMemberId(testMemberId);
|
||||||
|
console.log('Encrypted Member ID:', encrypted);
|
||||||
|
|
||||||
|
const decrypted = decryptMemberId(encrypted);
|
||||||
|
console.log('Decrypted Member ID:', decrypted);
|
||||||
|
|
||||||
|
console.log('Encryption/Decryption Test:', testMemberId === decrypted ? 'PASSED' : 'FAILED');
|
||||||
|
|
||||||
|
// Test API URL
|
||||||
|
const apiUrl = `http://localhost:3000/api/member-lookup?encryptedId=${encodeURIComponent(encrypted)}`;
|
||||||
|
console.log('\nTest API URL:', apiUrl);
|
||||||
|
console.log('\nTo test the API, make a GET request to the above URL after starting the development server.');
|
||||||
|
console.log('Expected response format:');
|
||||||
|
console.log(JSON.stringify({
|
||||||
|
success: true,
|
||||||
|
message: "Member information retrieved successfully",
|
||||||
|
data: {
|
||||||
|
name: "John Doe",
|
||||||
|
gender: "m",
|
||||||
|
planDelay: 1,
|
||||||
|
planStart: "Mon, 01 Jan 2024 00:00:00 GMT",
|
||||||
|
planStatus: "active",
|
||||||
|
planExpAt: "Thu, 01 Feb 2024 00:00:00 GMT"
|
||||||
|
}
|
||||||
|
}, null, 2));
|
||||||
Loading…
Reference in New Issue
Block a user