Compare commits

..
299 Commits
Author SHA1 Message Date
Majdalkilany0 1f521dfc41 Merge pull request #98 from Zod-Alkhair/feature/allowance-scheduling-cron-queue
feat(allowance): add dead letter exchange to allowance queue configur…
2026-02-04 15:08:27 +03:00
Abdalhamid Alhamad f7f22de65c feat(allowance): add dead letter exchange to allowance queue configuration
- Introduced ALLOWANCE_RETRY_EXCHANGE for handling message retries in the allowance worker service.
- Updated queue assertion to include dead letter exchange and routing key for improved message processing reliability.
2026-02-04 15:05:55 +03:00
Majdalkilany0 0640c8b59a Merge pull request #97 from Zod-Alkhair/feature/allowance-scheduling-cron-queue
feat(allowance): enhance logging in allowance queue and worker services
2026-02-04 11:45:45 +03:00
Abdalhamid Alhamad 6e11812925 feat(allowance): enhance logging in allowance queue and worker services
- Added detailed logging for enqueueing allowance jobs, processing jobs, and handling errors.
- Improved validation checks with warnings for invalid payloads and schedules.
- Enhanced logging for credit creation and transfer processes, including success and failure scenarios.
- Updated cron job logging to track the processing of due schedules and completion status.
2026-02-04 11:29:20 +03:00
Majdalkilany0 75e0f14bd9 Merge pull request #96 from Zod-Alkhair/feature/allowance-scheduling-cron-queue
feat(allowance): add delete API and configurable test intervals
2026-02-03 12:36:02 +03:00
Abdalhamid Alhamad 4d7549d02e feat(allowance): add delete API and configurable test intervals
- Add DELETE /guardians/me/allowances/:scheduleId endpoint
- Add ALLOWANCE_TEST_MODE env variable for testing intervals:
  - true: DAILY=5min, WEEKLY=10min, MONTHLY=15min
  - false: DAILY=1day, WEEKLY=1week, MONTHLY=1month
- Add deleteById to repository and deleteSchedule to service
2026-02-03 12:34:33 +03:00
Majdalkilany0 64a6cc9ddd Merge pull request #95 from Zod-Alkhair/feature/notification-system-fcm-registration
feat: add profile update notification handling for child users
2026-02-01 14:46:49 +03:00
Abdalhamid Alhamad 2ab9554c0c feat: add profile update notification handling for child users
- Implemented logic to skip notifications for child users (roles.JUNIOR) when their profiles are updated, preventing unnecessary notifications to both child and parent.
- Enhanced logging to indicate when notifications are skipped for child users.
2026-02-01 14:44:42 +03:00
Majdalkilany0 a7dee2dc1e Merge pull request #94 from Zod-Alkhair/feature/help-support-faq-lookup
fix: update phone number change instructions in help/support FAQs for…
2026-02-01 14:25:29 +03:00
Abdalhamid Alhamad 1822f074c6 fix: update phone number change instructions in help/support FAQs for clarity and localization 2026-02-01 14:24:35 +03:00
Majdalkilany0 95d0f0f4b0 Merge pull request #93 from Zod-Alkhair/feature/allowance-scheduling-cron-queue
feat(allowance): add GET and PATCH endpoints for allowance schedules
2026-02-01 13:22:19 +03:00
Abdalhamid Alhamad 799b9b883d feat(allowance): add GET and PATCH endpoints for allowance schedules
- GET /guardians/me/allowances: list all children grouped by schedule status
  - withSchedule: children with configured allowances (flattened response)
  - withoutSchedule: children without allowances
  - monthlyTotal: sum of active schedules converted to monthly equivalent

- GET /guardians/me/allowances/summary: lightweight endpoint for home page
  - nextPaymentAt: nearest upcoming payment date
  - monthlyTotal: monthly equivalent total

- PATCH /guardians/me/allowances/:scheduleId: update existing schedule
  - supports partial updates (amount, frequency, status)
  - recalculates nextRunAt when frequency or status changes

- Added interfaces directory for type definitions
- Added response DTOs with flattened junior + schedule data
2026-02-01 13:21:02 +03:00
Majdalkilany0 8d56a8da0f Merge pull request #92 from Zod-Alkhair/feature/allowance-scheduling-cron-queue
feat: add allowance scheduling with cron, queue, and worker
2026-01-28 16:04:56 +03:00
Abdalhamid Alhamad 1a0bd0bf91 feat: add allowance scheduling with cron, queue, and worker 2026-01-28 16:03:41 +03:00
Majdalkilany0 52fb5f3984 Merge pull request #91 from Zod-Alkhair/feature/help-support-faq-lookup
fix build error
2026-01-25 14:13:14 +03:00
Abdalhamid Alhamad db946b9531 fix build error 2026-01-25 14:10:46 +03:00
Majdalkilany0 d34508dca9 Merge pull request #90 from Zod-Alkhair/feature/help-support-faq-lookup
feat(lookup): add localized help/support FAQs
2026-01-25 13:16:36 +03:00
Abdalhamid Alhamad 25ede3c9e7 feat(lookup): add localized help/support FAQs 2026-01-25 13:15:59 +03:00
Majdalkilany0 47b825c4b2 Merge pull request #89 from Zod-Alkhair/feature/notification-system-fcm-registration
feat: enhance card service and transaction notification for shared ac…
2026-01-20 16:28:42 +03:00
Abdalhamid Alhamad f5c3b03264 feat: enhance card service and transaction notification for shared account handling
- Updated CardService to differentiate between shared and separate accounts during card control updates, optimizing balance allocation.
- Enhanced TransactionNotificationListener to accurately reflect balance based on account structure for internal transfers and top-ups.
- Improved logging for better traceability of account operations and balance calculations.
2026-01-20 16:27:32 +03:00
Majdalkilany0 6a250efd5e Merge pull request #88 from Zod-Alkhair/feature/notification-system-fcm-registration
feat: enhance transaction notification listener for internal transfer…
2026-01-20 15:21:45 +03:00
Abdalhamid Alhamad a09b84e475 feat: enhance transaction notification listener for internal transfer support
- Updated TransactionNotificationListener to differentiate between internal transfers and external top-ups for child accounts.
- Added new notification scopes and messages for internal transfers from parent to child.
- Improved balance retrieval logic to ensure accurate account balances are displayed in notifications.
- Enhanced localization support by adding relevant keys for internal transfer notifications in both English and Arabic.
2026-01-20 15:20:58 +03:00
Majdalkilany0 604cb7ce25 Merge pull request #87 from Zod-Alkhair/feature/notification-system-fcm-registration
fix the messages
2026-01-20 14:42:03 +03:00
Abdalhamid Alhamad 4305c4b75f fix the messages 2026-01-20 14:40:16 +03:00
Majdalkilany0 ef5572440c Merge pull request #86 from Zod-Alkhair/fix/spending-history-junior-id-query
fix: correct junior ID to customer ID mapping in transaction queries
2026-01-20 12:50:34 +03:00
Abdalhamid Alhamad 64623c7cea fix: correct junior ID to customer ID mapping in transaction queries
Fixed spending history and related transaction queries that were incorrectly
using juniorId as customerId. The queries now properly join through the
Customer -> Junior relationship to filter by junior ID.

Affected methods:
- getTransactionsForCardWithinDateRange (spending history)
- findTransfersToJunior (transfers list)
- countTransfersToJunior (transfers count)
- findTransactionById (transaction details)

This fixes the spending history endpoint which was returning empty results
due to ID mismatch between Junior entity ID and Customer entity ID.

Performance impact: Minimal (~1-2ms overhead from additional joins on
indexed foreign keys). The queries now return correct results instead of
0 results.
2026-01-20 12:39:10 +03:00
Majdalkilany0 4ca8123a67 Merge pull request #85 from Zod-Alkhair/feature/notification-system-fcm-registration
feat: refine transaction notification listener for improved balance c…
2026-01-14 16:57:36 +03:00
Abdalhamid Alhamad 7c9e0f0b51 feat: refine transaction notification listener for improved balance calculations
- Updated TransactionNotificationListener to differentiate between child top-up and spending notifications, ensuring accurate balance retrieval.
- Enhanced error handling and fallback mechanisms for both child and parent account balance fetching.
- Improved logging to provide detailed insights into balance calculations, including available balance after accounting for reserved amounts.
2026-01-14 16:56:22 +03:00
Majdalkilany0 e734060c52 Merge pull request #84 from Zod-Alkhair/feature/notification-system-fcm-registration
feat: improve transaction notification listener for accurate balance …
2026-01-14 16:33:05 +03:00
Abdalhamid Alhamad 1086166e04 feat: improve transaction notification listener for accurate balance retrieval
- Enhanced TransactionNotificationListener to fetch updated balances for child and parent accounts by bypassing entity cache.
- Implemented error handling and fallback mechanisms to ensure reliable balance notifications.
- Updated logging for better traceability of balance fetching processes.
2026-01-14 16:31:47 +03:00
Majdalkilany0 5e6c8d96de Merge pull request #83 from Zod-Alkhair/feature/notification-system-fcm-registration
feat: add timezone support to user and device entities
2026-01-14 16:12:59 +03:00
Abdalhamid Alhamad 6d6dc1471f feat: add timezone support to user and device entities
- Introduced optional timezone fields in User and Device entities to store user preferences and device timezones.
- Updated request DTOs for login and user updates to include timezone information.
- Enhanced AuthService to handle timezone during device registration and updates.
- Added migration to incorporate timezone fields in the database schema.
2026-01-14 16:12:08 +03:00
Majdalkilany0 0f56381703 Merge pull request #82 from Zod-Alkhair/feature/notification-system-fcm-registration
feat: enhance card and transaction services for balance updates
2026-01-14 14:52:57 +03:00
Abdalhamid Alhamad 1b0d6cb284 feat: enhance card and transaction services for balance updates
- Added functionality to credit child account balance and decrease parent account balance in CardService.
- Updated TransactionService to reload card details for accurate balance after transactions.
- Improved TransactionNotificationListener to fetch updated balances for both child and parent accounts, ensuring accurate notifications.
2026-01-14 14:51:45 +03:00
Majdalkilany0 887bd20217 Merge pull request #81 from Zod-Alkhair/feature/notification-system-fcm-registration
feat: enhance card service validation and notification integration
2026-01-14 13:27:56 +03:00
Abdalhamid Alhamad c963b57904 feat: enhance card service validation and notification integration
- Added validation for card reference and limit in CardService to ensure data integrity.
- Improved error handling with detailed logging for invalid card states.
- Updated transaction notification listener to fetch parent account details and adjust balance notifications accordingly.
- Enhanced notification creation process to include status management for better tracking.
2026-01-14 13:27:02 +03:00
Majdalkilany0 2e21acac7f Merge pull request #80 from Zod-Alkhair/feature/notification-system-fcm-registration
feat: implement KYC and card notification events
2026-01-14 12:57:11 +03:00
Abdalhamid Alhamad 145e6c62b8 feat: implement KYC and card notification events
- Added KycNotificationListener to handle notifications for KYC approval and rejection events.
- Introduced CardNotificationListener to manage notifications for card creation and blocking events.
- Enhanced CardService to emit events for card creation and blocking, integrating with the new notification system.
- Updated notification constants and interfaces to include new KYC and card-related events.
- Improved notification message formatting and added localization support for new events.
2026-01-14 12:31:48 +03:00
Majdalkilany0 652359b1bf Merge pull request #79 from Zod-Alkhair/feature/notification-system-fcm-registration
feat: enhance transaction notification logging and error handling
2026-01-12 16:48:13 +03:00
Abdalhamid Alhamad 45acf73a4a feat: enhance transaction notification logging and error handling
- Added console logging for emitted transaction creation events in TransactionService.
- Improved error handling in TransactionNotificationListener for i18n translation failures, providing fallback messages.
- Updated amount parsing in MoneyRequestNotificationListener to ensure consistent handling of string and numeric values.
2026-01-12 16:47:28 +03:00
Majdalkilany0 2d6524be9f Merge pull request #78 from Zod-Alkhair/feature/notification-system-fcm-registration
refactor: standardize notification message formatting
2026-01-12 16:30:32 +03:00
Abdalhamid Alhamad d3ff755439 refactor: standardize notification message formatting
- Updated notification message arguments to use consistent object syntax for better readability.
- Modified Arabic and English translation files to reflect the new argument format in notification messages.
2026-01-12 16:28:26 +03:00
Majdalkilany0 3ab00dfc29 Merge pull request #76 from Zod-Alkhair/feature/notification-system-fcm-registration
feat: implement money request notification system
2026-01-12 16:15:19 +03:00
Abdalhamid Alhamad 21653efc46 feat: implement money request notification system
- Added MoneyRequestNotificationListener to handle notifications for money request events (created, approved, declined).
- Introduced new notification event constants for money requests.
- Updated notification interfaces to include money request event payloads.
- Enhanced existing notification system to support money request notifications, notifying parents and children appropriately.
- Updated device service to support finding devices by ID for improved functionality.
2026-01-12 16:07:48 +03:00
Majdalkilany0 11b2b25adc Merge pull request #75 from Zod-Alkhair/feature/notification-system-fcm-registration
feat: enhance Redis module exports for pub/sub functionality
2026-01-11 14:40:12 +03:00
Abdalhamid Alhamad 63b0a42eca feat: enhance Redis module exports for pub/sub functionality
- Added 'REDIS_PUBLISHER' and 'REDIS_SUBSCRIBER' to the exports of RedisModule to improve pub/sub capabilities.
2026-01-11 14:38:59 +03:00
Majdalkilany0 ed8cf4b4f8 Merge pull request #74 from Zod-Alkhair/feature/notification-system-fcm-registration
Feature/notification system fcm registration
2026-01-11 14:31:58 +03:00
Abdalhamid Alhamad b1cda5e7dc feat: Complete Phase 2 notification system implementation
- Implement messaging system factory pattern
- Fix all transaction notification blockers
- Complete listener logic for all notification types
2026-01-11 11:17:08 +03:00
Abdalhamid Alhamad 2c8de913f8 refactor: update notification titles and enhance notification creation process
- Simplified notification titles by removing emojis for better clarity.
- Modified createNotification method to include automatic publishing to Redis, improving notification delivery.
- Updated email and OTP notification methods to leverage the new createNotification functionality.
2026-01-06 16:22:21 +03:00
Majdalkilany0 98f6aaf01f Merge pull request #73 from Zod-Alkhair/feature/notification-system-fcm-registration
add eveint lestiner to the parent
2026-01-06 14:52:52 +03:00
Abdalhamid Alhamad 170aa903c7 add eveint lestiner to the parent 2026-01-06 14:51:44 +03:00
Majdalkilany0 16f8756b74 Merge pull request #72 from Zod-Alkhair/feature/notification-system-fcm-registration
merge conflect
2026-01-06 12:58:49 +03:00
Abdalhamid Alhamad 2f74aa36a9 merge conflect 2026-01-06 12:57:13 +03:00
Majdalkilany0 f849003142 Merge pull request #71 from Zod-Alkhair/feature/notification-system-fcm-registration
Feature/notification system fcm registration
2026-01-06 12:54:45 +03:00
Abdalhamid Alhamad 2562515574 Merge branch 'dev' of github.com:HamzaSha1/zod-backend into feature/notification-system-fcm-registration 2026-01-06 12:53:44 +03:00
Abdalhamid Alhamad 93b509b256 feat: add notification event handling and notification factory service
- Introduce constants for notification event names
- Implement interfaces for transaction created events
- Create a transaction notification listener to handle transaction notifications
- Develop a notification factory service for sending notifications based on user preferences
- Add a migration to include a data column in the notifications table
2026-01-06 12:38:19 +03:00
Abdalhamid Alhamad 9c93a35093 feat: implement notification system with FCM token registration
- Add FCM token registration during login/signup
- Implement transaction notification listeners
- Add notification data column to database
- Update Firebase service with data payload support
- Add transaction notification scopes
- Update card repository to load relations for notifications
2026-01-06 12:29:01 +03:00
Majdalkilany0 d77d59a793 Merge pull request #70 from Zod-Alkhair/feature/kyc-onboarding
Feature/kyc onboarding
2025-12-18 14:26:27 +03:00
Abdalhamid Alhamad 110a6fb0ee refactor: remove address fields from customer entity and related services
- Removed address-related fields from Customer entity, DTOs, and services to streamline KYC process.
- Updated KYC initiation and customer update logic to default to Saudi Arabia for country and use fixed address values.
- Added migration to drop address columns from the database.
2025-12-18 12:35:32 +03:00
Majdalkilany0 83787c7c67 Merge pull request #69 from Zod-Alkhair/feature/kyc-onboarding
feat: enhance KYC process with external customer ID validation
2025-12-17 12:56:32 +03:00
Abdalhamid Alhamad 24bcb10d76 feat: enhance KYC process with external customer ID validation
- Added validation to ensure customer has a neoleapExternalCustomerId before card creation.
- Updated KYC status update to include neoleapExternalCustomerId in the customer record.
- Enhanced application info to include ExternalCorporateId for better integration with Neoleap.
2025-12-17 12:51:20 +03:00
Majdalkilany0 a3cdf50cb7 Merge pull request #68 from Zod-Alkhair/feature/kyc-onboarding
refactor: remove obsolete customer fields and update migration
2025-12-16 16:42:30 +03:00
Abdalhamid Alhamad cfd02e8c30 refactor: remove obsolete customer fields and update migration
- Removed unused fields: sourceOfIncome, profession, and professionType from Customer entity and DTOs.
- Updated KYC callback mock to reflect the removal of professionType.
- Added migration to drop the corresponding columns from the database.
2025-12-16 16:40:13 +03:00
Majdalkilany0 0fb76d712d Merge pull request #67 from Zod-Alkhair/feature/kyc-onboarding
Feature/kyc onboarding
2025-12-16 14:59:28 +03:00
Abdalhamid Alhamad 5e708c16fe chore: remove migration from wrong directory
Migration already exists in correct location: src/db/migrations/
2025-12-16 14:57:03 +03:00
Abdalhamid Alhamad fe11f35b32 feat: send the adress data to noleap 2025-12-16 14:51:21 +03:00
Abdalhamid Alhamad 3200f60821 feat: Complete KYC implementation with address fields
- Added address fields to registration (verify-user DTO)
- Added address fields to KYC initiation (initiate-kyc DTO)
- Added national_id column to kyc_transactions table
- Changed duplicate KYC check from customerId to nationalId
- Added KYC webhook endpoint (/api/neoleap-webhooks/kyc)
- Added webhook processing logic
- Updated customer service to save address during registration and KYC
- Added validation to require address before card creation
- Removed duplicate src/migrations/ directory
2025-12-16 14:44:07 +03:00
Majdalkilany0 24521c4223 Merge pull request #66 from Zod-Alkhair/chore/remove-email-dob-from-signup
chore: remove email and dob from guardian signup flow
2025-12-11 12:15:54 +03:00
Abdalhamid Alhamad e8127970f6 chore: remove email and dob from guardian signup flow 2025-12-11 12:13:51 +03:00
Abdalhamid Alhamad 07d4a83cf9 test ssh 2025-12-07 20:18:59 +03:00
abdalhamid99 ce1f6341b7 Merge pull request #65 from HamzaSha1/dev
Dev to main
2025-11-26 09:58:33 +03:00
abdalhamid99 2a62787c3b Merge pull request #63 from HamzaSha1/feature/kyc-onboarding-metadata
refactor: remove unused PoiValidationRule class from KycMetadataRespo
2025-11-18 15:16:33 +03:00
Abdalhamid Alhamad 91dea22f45 refactor: remove unused PoiValidationRule class from KycMetadataResponseDto 2025-11-18 15:14:47 +03:00
abdalhamid99 ef28c75f9b Merge pull request #62 from HamzaSha1/feature/kyc-onboarding-metadata
feat: add KYC onboarding metadata endpoint with POI validation
2025-11-18 15:06:50 +03:00
Abdalhamid Alhamad c007ac584f feat: add KYC onboarding metadata endpoint with POI validation 2025-11-18 15:03:42 +03:00
abdalhamid99 d2d83549b2 Merge pull request #61 from HamzaSha1/fix/junior-profile-picture-refresh-on-update
Enhance profile picture handling in JuniorService to ensure foreign
2025-11-09 12:43:54 +03:00
Abdalhamid Alhamad 506974afc8 Enhance profile picture handling in JuniorService to ensure foreign key consistency and validate document ownership before assignment. 2025-11-09 12:42:48 +03:00
abdalhamid99 95f8cfbfdf Merge pull request #60 from HamzaSha1/fix/junior-profile-picture-refresh-on-update
Update return value in updateJunior method to fetch updated junior dtails by ID instead of returning the junior object directly.
2025-11-09 12:26:44 +03:00
Abdalhamid Alhamad 8b00cda23d Update return value in updateJunior method to fetch updated junior details by ID instead of returning the junior object directly. 2025-11-09 12:25:37 +03:00
abdalhamid99 12cc88a50e Merge pull request #59 from HamzaSha1/money-request-to-use-the-parint-account
Refactor balance check in increaseReservedBalance method to delegate …
2025-11-02 12:41:51 +03:00
Abdalhamid Alhamad 2172051093 Refactor balance check in increaseReservedBalance method to delegate validation to the caller, improving clarity and responsibility separation. 2025-11-02 12:41:16 +03:00
abdalhamid99 a6a573957c Merge pull request #58 from HamzaSha1/money-request-to-use-the-parint-account
add more loggs
2025-11-02 12:35:31 +03:00
Abdalhamid Alhamad d6fb5f48d9 add more loggs 2025-11-02 12:34:41 +03:00
abdalhamid99 b0011eb7cc Merge pull request #57 from HamzaSha1/money-request-to-use-the-parint-account
Money request to use the parint account
2025-11-02 12:07:13 +03:00
Abdalhamid Alhamad 99af65a300 money-request to use the parent card 2025-11-02 11:57:41 +03:00
abdalhamid99 0c9b40132a Merge pull request #56 from HamzaSha1/ZOD-344-after-a-child-completes-registration-using-the-qr-code-the-same-qr-code-remains-valid-and-allows-the-child-to-register-again-instead-of-expiring
ZOD-344-Add QR code validation error handling and localization support
2025-11-02 11:02:25 +03:00
Abdalhamid Alhamad 3b295ea79f ZOD-344-Add QR code validation error handling and localization support
- Introduced new error handling for already used or expired QR codes in JuniorService.
- Added corresponding localization entries in Arabic and English app.json files for QR code validation messages.
2025-11-02 10:52:43 +03:00
abdalhamid99 5ffe18ede3 Merge pull request #54 from HamzaSha1/fix/verfy-email
Implement OTP generation and email verification logic in UserService
2025-10-28 16:17:51 +03:00
Abdalhamid Alhamad a3a61b4923 Implement OTP generation and email verification logic in UserService 2025-10-28 15:52:24 +03:00
abdalhamid99 39d5fc1869 Merge pull request #52 from HamzaSha1/ZOD-349-weekly-spending-data-not-displaying-in-the-child-profile-parent-view
Enhance weekly summary functionality to accept optional date range pa…
2025-10-28 11:22:52 +03:00
Abdalhamid Alhamad 05a6ad2d84 Enhance weekly summary functionality to accept optional date range parameters in CardService, TransactionService, JuniorService, and JuniorController. Update API documentation to reflect new query parameters for start and end dates. 2025-10-28 11:20:49 +03:00
abdalhamid99 5649d24724 Merge pull request #50 from HamzaSha1/ZOD-349-weekly-spending-data-not-displaying-in-the-child-profile-parent-view
git checkout -b ZOD-349-weekly-spending-data-not-displaying-in-the-child-profile-parent-view
2025-10-26 16:05:00 +03:00
Abdalhamid Alhamad bbeece9e03 git checkout -b ZOD-349-weekly-spending-data-not-displaying-in-the-child-profile-parent-view 2025-10-26 13:14:35 +03:00
abdalhamid99 596562f6dc Merge pull request #48 from HamzaSha1/feat/neoleap-integration
Feat/neoleap integration
2025-10-21 14:56:38 +03:00
abdalhamid99 10de8f69c9 Merge pull request #47 from HamzaSha1/ZOD-341-junior-a-child-can-edit-their-email-to-an-existing-email-causing-multiple-child-accounts-to-share-the-same-login
Remove duplicate email cleanup logic and add unique constraint to use…
2025-10-21 14:15:03 +03:00
Abdalhamid Alhamad 8a6b1cc900 Remove duplicate email cleanup logic and add unique constraint to user email 2025-10-21 14:10:14 +03:00
abdalhamid99 d16ae66252 Merge pull request #46 from HamzaSha1/ZOD-341-junior-a-child-can-edit-their-email-to-an-existing-email-causing-multiple-child-accounts-to-share-the-same-login
ZOD-341-Add unique constraint to user email and clean up duplicates
2025-10-21 10:51:12 +03:00
Abdalhamid Alhamad e966f95463 ZOD-341-Add unique constraint to user email and clean up duplicates 2025-10-21 10:49:43 +03:00
abdalhamid99 2714255dd1 Merge pull request #45 from HamzaSha1/ZOD-341-junior-a-child-can-edit-their-email-to-an-existing-email-causing-multiple-child-accounts-to-share-the-same-login
ZOD-341 Add email uniqueness validation to prevent duplicate emails
2025-10-20 14:31:11 +03:00
abdalhamid99 39a0b131b8 Merge pull request #44 from HamzaSha1/ZOD-341-junior-a-child-can-edit-their-email-to-an-existing-email-causing-multiple-child-accounts-to-share-the-same-login
Zod 341 junior a child can edit their email to an existing email causing multiple child accounts to share the same login
2025-10-20 14:27:40 +03:00
Abdalhamid Alhamad 4f778f7904 * ZOD-341-junior-a-child-can-edit-their-email-to-an-existing-email-causing-multiple-child-accounts-to-share-the-same-login 2025-10-20 14:25:53 +03:00
abdalhamid99 7e9bc397a9 Merge pull request #43 from HamzaSha1/ZOD-204-view-spending-from-child-login
git checkout -b ZOD-204-view-spending-from-child-login
2025-10-20 10:30:27 +03:00
abdalhamid99 7bfc14f0d9 Merge pull request #42 from HamzaSha1/ZOD-204-view-spending-from-child-login
ZOD-204-view-spending-from-child-login
2025-10-19 15:44:16 +03:00
Abdalhamid Alhamad d2e084d3e4 git checkout -b ZOD-204-view-spending-from-child-login 2025-10-19 15:26:47 +03:00
abdalhamid99 f81714a525 Merge pull request #41 from HamzaSha1/ZOD-339-child-profile-gender-update-is-not-reflected-after-editing
ZOD-339-child-profile-gender-update-is-not-reflected-after-editing
2025-10-19 11:07:39 +03:00
abdalhamid99 f3282a680b Merge pull request #40 from HamzaSha1/ZOD-339-child-profile-gender-update-is-not-reflected-after-editing
Zod 339 child profile gender update is not reflected after editing
2025-10-19 11:02:40 +03:00
Abdalhamid Alhamad 7b57277a7f ZOD-339-child-profile-gender-update-is-not-reflected-after-editing 2025-10-19 11:01:52 +03:00
abdalhamid99 fdd2e23669 Merge pull request #39 from HamzaSha1/ZOD-333-junior-incorrect-relationship-label-displayed-as-child-instead-of-daughter-or-son-in-child-confirmation-details-after-the-scan-the-qr-code
ZOD-333-junior-incorrect-relationship-label-displayed-as-child-instea
2025-10-19 10:47:51 +03:00
abdalhamid99 d70ab09960 Merge pull request #38 from HamzaSha1/ZOD-333-junior-incorrect-relationship-label-displayed-as-child-instead-of-daughter-or-son-in-child-confirmation-details-after-the-scan-the-qr-code
Zod 333 junior incorrect relationship label displayed as child instead of daughter or son in child confirmation details after the scan the qr code
2025-10-19 09:58:57 +03:00
Abdalhamid Alhamad 297a2fe5ad ZOD-333-junior-incorrect-relationship-label-displayed-as-child-instead-of-daughter-or-son-in-child-confirmation-details-after-the-scan-the-qr-code 2025-10-19 09:57:35 +03:00
abdalhamid99 33b4f13ec8 Merge pull request #37 from HamzaSha1/feat/neoleap-integration
Feat/neoleap integration
2025-10-16 14:50:23 +03:00
abdalhamid99 310233c519 Merge pull request #36 from HamzaSha1/ZOD-309-child-transaction-history-parent-→-child-transfers
ZOD-309-child-transaction-history-parent-→-child-transfers
2025-10-16 12:26:50 +03:00
Abdalhamid Alhamad 15621124ad ZOD-309-child-transaction-history-parent-→-child-transfers 2025-10-16 12:25:16 +03:00
abdalhamid99 7fc1918de0 Merge pull request #35 from HamzaSha1/feat/parent-topups-and-child-transfers
feat: add guardian transactions feature with response DTOs and service integration
2025-10-15 14:17:08 +03:00
Abdalhamid Alhamad f6fa74897a feat: add guardian transactions feature with response DTOs and service integration 2025-10-15 14:14:59 +03:00
abdalhamid99 dd6886ff2b Merge pull request #34 from HamzaSha1/feat/neoleap-integration
match the neoleap-integration branch with dev
2025-10-14 12:20:19 +03:00
abdalhamid99 649191f3f4 Merge pull request #33 from HamzaSha1/fix/customer-gender-missing-in-get-profile
fix: add gender property to UserResponseDto
2025-10-14 12:14:01 +03:00
abdalhamid99 183f6b4475 fix: add gender property to UserResponseDto
fix: add gender property to UserResponseDto
2025-10-12 16:06:39 +03:00
Abdalhamid Alhamad 8f601b26ae fix: add gender property to UserResponseDto 2025-10-12 16:03:25 +03:00
Abdalhameed Ahmad 918b15c315 fix: add swagger 2025-09-23 09:00:41 +03:00
Abdalhameed Ahmad 1830d92cbd feat: weekly stats for junior 2025-09-23 08:56:57 +03:00
Abdalhameed Ahmad 44124b9964 Merge branch 'dev' of github.com:HamzaSha0/zod-backend into dev 2025-09-18 10:03:47 +03:00
Abdalhameed Ahmad 454ded627f fix: fix transfer to child bug 2025-09-16 21:01:32 +03:00
Abdalhameed Ahmad f1484e125b feat: soft delete junior 2025-09-15 09:02:56 +03:00
Abdalhameed Ahmad df4d2e3c1f feat: get card by child id 2025-09-15 08:47:56 +03:00
Abdalhameed Ahmad 872d231f72 feat: show embossing information for child cards 2025-09-09 21:45:37 +03:00
Abdalhameed Ahmad cc4c8254f6 feat: view child active cards 2025-09-09 21:37:55 +03:00
Abdalhameed Ahmad 039c95aa56 fix: calculating child and parent balance 2025-09-09 20:31:48 +03:00
Abdalhameed Ahmad e1f50decfa feat: money requests 2025-09-08 21:38:11 +03:00
abdalhamid99 11712bedf3 Merge pull request #31 from HamzaSha1/feat/neoleap-integration
Feat/neoleap integration
2025-09-08 21:34:46 +03:00
Abdalhameed Ahmad e6642b5a15 fix: fix controller name 2025-09-07 21:47:17 +03:00
Abdalhameed Ahmad 954aa422a2 fix: fix mock request for funding decorators 2025-09-07 21:40:55 +03:00
Abdalhameed Ahmad 15a48e4884 fix: validate card spending limit before transfering to child 2025-09-07 21:18:49 +03:00
Abdalhameed Ahmad d768da70f2 feat: transfer to parent 2025-09-07 20:23:11 +03:00
Abdalhameed Ahmad 9b0e1791da feat: transfer money to child 2025-09-07 20:14:28 +03:00
Abdalhameed Ahmad 44b5937f7a feat: finalize update junior 2025-09-07 18:13:28 +03:00
Abdalhameed Ahmad edddc2f457 feat: update junior 2025-09-07 09:12:14 +03:00
Abdalhameed Ahmad 88730a2b2b fix: fix create application mock 2025-08-26 19:17:00 +03:00
Abdalhameed Ahmad 3df34c0017 fix: fix duplicate iban 2025-08-26 12:19:47 +03:00
Abdalhameed Ahmad 7dd309e0e3 fix: fix null assertion in creating junior 2025-08-24 20:14:59 +03:00
Abdalhameed Ahmad 4552a7fc93 fix: fix issue with customer relationship in creating junior 2025-08-24 20:12:57 +03:00
Abdalhameed Ahmad 740135051d feat: create card for children 2025-08-24 20:01:07 +03:00
Abdalhameed Ahmad 3222aa4a66 fix: fix card embossing info endpoint 2025-08-24 18:40:49 +03:00
Abdalhameed Ahmad 6602414779 feat: finialize creating juniors 2025-08-23 21:52:59 +03:00
Abdalhameed Ahmad 7291447c5a Merge branch 'dev' into feat/neoleap-integration 2025-08-23 20:12:48 +03:00
Abdalhameed Ahmad d437b21dc3 fix: make kyc run on mocks 2025-08-23 19:30:29 +03:00
Abdalhamid Alhamad e06642225a feat: working on creating parent card 2025-08-14 14:40:08 +03:00
abdalhamid99 c06086f899 Merge pull request #30 from HamzaSha1/dev
Dev
2025-08-11 18:22:34 +03:00
abdalhamid99 e775561a89 Merge pull request #29 from HamzaSha1/main
Merge main into dev
2025-08-11 18:21:52 +03:00
abdalhamid99 241f1ce427 Merge branch 'dev' into main 2025-08-11 18:21:43 +03:00
abdalhamid99 d883bd2d9a Update junior.repository.ts 2025-08-11 18:19:57 +03:00
abdalhamid99 cd800ff8b8 Update customer.repository.ts 2025-08-11 18:18:48 +03:00
Abdalhamid Alhamad 05a9f04ac8 fix: fix import in migration index 2025-08-11 16:23:19 +03:00
Abdalhamid Alhamad dcc9077392 fix: rename migration timestamp 2025-08-11 16:22:20 +03:00
Abdalhamid Alhamad 681d1e5791 fix: fix seed default avatar migration 2025-08-11 16:16:26 +03:00
Abdalhamid Alhamad bf505a65bf fix: fix invalid imports 2025-08-11 16:13:53 +03:00
Abdalhamid Alhamad 6bf32d27c7 Merge branch 'dev' of github.com:HamzaSha1/zod-backend into dev 2025-08-11 15:33:39 +03:00
Abdalhamid Alhamad ac63d4cdc7 refactor: refactor the code 2025-08-11 15:33:32 +03:00
Abdalhamid Alhamad 150027fb71 Merge branch 'main' into dev 2025-08-11 15:25:16 +03:00
Abdalhamid Alhamad e8ee74d0d7 refactor: remove unsed code 2025-08-11 15:15:41 +03:00
abdalhamid99 5f2e06edf9 Merge pull request #28 from HamzaSha1/feat/neoleap-integration
Feat/neoleap integration
2025-08-10 16:16:32 +03:00
Abdalhamid Alhamad 99ad17f0f9 feat: add change password api 2025-08-07 15:25:45 +03:00
Abdalhamid Alhamad ee7b365527 feat: kyc process 2025-08-07 14:23:33 +03:00
Abdalhamid Alhamad 275984954e feat: working on edit profile ticket 2025-08-05 17:53:38 +03:00
abdalhamid99 6f7fb2bdcd Merge pull request #27 from HamzaSha1/feat/neoleap-integration
Merge neoleap-integration into dev
2025-08-03 16:24:46 +03:00
Abdalhamid Alhamad 1e2b859b92 feat: finish generating signed url for document upload flow 2025-08-03 16:18:06 +03:00
Abdalhamid Alhamad 4cc52a1c07 fix: add swagger doc to verify otp api 2025-08-03 14:50:04 +03:00
Abdalhamid Alhamad 7461af20dd feat: edit forget password flow 2025-08-03 14:48:14 +03:00
Abdalhamid Alhamad f65a7d2933 feat: generate upload signed url for oci 2025-08-03 14:21:14 +03:00
Abdalhamid Alhamad fce720237f feat: add vpan to card entity 2025-08-03 11:53:16 +03:00
Abdalhamid Alhamad 5e0a4e6bd1 feat: fix update card status webhook 2025-07-31 14:42:06 +03:00
Abdalhamid Alhamad f9776e60cf fix: save transaction file 2025-07-31 14:11:53 +03:00
Abdalhamid Alhamad 7e63abb2fb feat: add-account-details 2025-07-31 14:07:01 +03:00
Abdalhamid Alhamad a245545811 feat: add login and forget password and refactor code 2025-07-30 15:40:40 +03:00
Abdalhamid Alhamad 4cb5814cd3 fix: organize migrations 2025-07-30 14:18:10 +03:00
Abdalhamid Alhamad 9e06ea4d71 Merge branch 'dev' into feat/neoleap-integration 2025-07-30 14:09:00 +03:00
Abdalhamid Alhamad cff87c4ecd rollup migrations into one 2025-07-30 13:08:31 +03:00
Abdalhamid Alhamad 1541c374ed feat: fix swagger examples 2025-07-27 13:26:21 +03:00
Abdalhamid Alhamad c493bd57e1 feat: onboarding signup journey 2025-07-27 13:15:54 +03:00
Abdalhamid Alhamad bf43e62b17 feat: handle card status changed webhook 2025-07-21 15:30:55 +03:00
Abdalhamid Alhamad 5a780eeb17 feat/working on update card control 2025-07-14 11:57:51 +03:00
Abdalhamid Alhamad 038b8ef6e3 feat: finish working on account transaction webhook 2025-07-09 13:31:08 +03:00
Abdalhamid Alhamad 3b3f8c0104 fix: remove host from request 2025-07-07 16:34:45 +03:00
Abdalhamid Alhamad 2770cf8774 fix:fix card migration 2025-07-07 12:06:01 +03:00
Abdalhamid Alhamad bea3ccfbbc Merge branch 'waiting-list' into feat/neoleap-integration 2025-07-06 16:45:37 +03:00
Abdalhamid Alhamad 492e538eb8 feat: send request via gateway 2025-07-06 16:44:23 +03:00
Abdalhamid Alhamad d3057beb54 feat: add transaction, card , and account entities 2025-07-02 18:42:38 +03:00
Abdalhamid Alhamad 19fa53c981 fix: fix apple client audience 2025-06-17 11:39:51 +03:00
Abdalhamid Alhamad d2cc02fb60 fix: localizze error messages 2025-06-17 09:43:36 +03:00
Abdalhamid Alhamad 4cbbfd8136 Merge branch 'waiting-list' into feat/neoleap-integration 2025-06-11 11:15:10 +03:00
Abdalhamid Alhamad 6c859a25d2 feat: handle oauth2 login 2025-06-04 14:52:09 +03:00
Abdalhamid Alhamad d1a6d3e715 feat: add test controller for integartion 2025-06-04 10:04:45 +03:00
Abdalhamid Alhamad 1ea1f42169 feat: finish create and inquire application api and handle response and errors 2025-06-03 14:51:36 +03:00
Abdalhamid Alhamad d4fe3b3fc3 feat: finish working on mocking inquire application api 2025-05-26 16:34:09 +03:00
Abdalhamid Alhamad b44bc5d5cc fix: localize customer already exist message 2025-05-26 15:24:20 +03:00
Abdalhamid Alhamad 9aa6c487ed Merge branch 'waiting-list' into feat/neoleap-integration 2025-05-26 12:11:53 +03:00
Abdalhamid Alhamad 42e4d75d70 feat: add country iso enum 2025-05-26 12:10:09 +03:00
Abdalhamid Alhamad a358cd2e7a feat: add neoleap service and mock create application api 2025-05-26 12:04:00 +03:00
Abdalhamid Alhamad 641a665beb Merge branch 'waiting-list' into feat/neoleap-integration 2025-05-21 09:59:18 +03:00
Abdalhamid Alhamad 49326e983f feat: handle new registration flow 2025-05-19 17:00:32 +03:00
Abdalhamid Alhamad 881d88c8d8 feat: add customer details to customer entity 2025-05-19 14:16:18 +03:00
Abdalhamid Alhamad 35ab3df7c1 feat: update create junior payload and add new document type 2025-05-14 14:53:22 +03:00
Abdalhamid Alhamad cbade0a87d feat: blacklist refresh tokens 2025-04-09 16:20:29 +03:00
Abdalhamid Alhamad 4c6ef17525 refactor: remove login by password and biometric 2025-04-09 15:07:48 +03:00
Abdalhamid Alhamad ffca6996fd feat: styling email template 2025-04-06 13:33:57 +03:00
Abdalhamid Alhamad a3f88c774c feat: handle notification using redis 2025-03-27 12:33:56 +03:00
Abdalhamid Alhamad ec38b82a7b feat: add waiting number and handle resent otp 2025-03-16 11:34:08 +03:00
Abdalhamid Alhamad 9b5f863577 add login flow for waiting list demo app 2025-03-04 14:42:02 +03:00
Abdalhamid Alhamad 54ce5b022d feat: adding enhancment for zod admin portal kyc and add customer additional fields 2025-03-02 10:49:58 +03:00
Abdalhamid Alhamad dae9cb6323 refactor: handle kyc journey for customers 2025-02-20 16:18:06 +03:00
Abdalhamid Alhamad 270753cfd7 fix: fix domain name for apple auth 2025-01-16 13:42:52 +03:00
Abdalhamid Alhamad 6b1cb3a84e fix: fix apibase url 2025-01-16 13:38:59 +03:00
Abdalhamid Alhamad ebd4b293e9 feat: client testing api , add ouath2 2025-01-16 13:35:22 +03:00
Abdalhamid Alhamad 87bb1a2709 feat: add apple login 2025-01-16 12:17:10 +03:00
Abdalhamid Alhamad 663e8972c4 feat: client test app, add apple login 2025-01-15 14:37:53 +03:00
Abdalhamid Alhamad 8ff9f921e8 feat: add localization for request dto validations and add accept lanuage header 2025-01-14 14:59:01 +03:00
Abdalhamid Alhamad 6d2d2b558a feat: add localization to expection messages 2025-01-14 12:39:19 +03:00
Abdalhamid Alhamad 5aa3d3774d refactor: enhance customer creation code 2025-01-13 16:42:27 +03:00
Abdalhamid Alhamad 221f3bae4f feat: add find task by id endpoint 2025-01-13 14:15:29 +03:00
Abdalhamid Alhamad 62621c1a15 feat: add validation for documents 2025-01-13 11:43:28 +03:00
Abdalhamid Alhamad 756e947c8a feat: handle google login 2025-01-09 15:14:09 +03:00
Abdalhamid Alhamad db02a28b4d hotfix: fix completed task filter 2025-01-07 14:18:08 +03:00
Ammar Qaffaf afc087ff08 api client for testing 2025-01-07 01:46:58 -05:00
Abdalhamid Alhamad ee433a5c8c fix: remove send email endpoint 2025-01-06 16:47:43 +03:00
Abdalhamid Alhamad 3ab0f179d8 feat: add smtp and fix dynamic link 2025-01-06 16:46:35 +03:00
Abdalhamid Alhamad 25ef549417 feat: prefix all apis 2025-01-05 14:46:49 +03:00
Abdalhamid Alhamad 084d39096c feat:integration brancjio dynamic links to junior qr code registration 2025-01-05 13:15:57 +03:00
Abdalhamid Alhamad eca84b4e75 fix: general fixes on date types and some typos 2025-01-05 10:38:19 +03:00
Abdalhamid Alhamad aefa866ae7 feat: add money request cronjob, and edit money request entity 2025-01-02 11:22:33 +03:00
abdalhamid99 557ef4cd33 Merge pull request #26 from HamzaSha1/feat/cron-lock
Feat/cron lock
2024-12-30 16:18:06 +03:00
Abdalhamid Alhamad eea6302dda Merge branch 'mvp-1' of github.com:HamzaSha1/zod-backend into feat/cron-lock 2024-12-30 16:10:28 +03:00
Abdalhamid Alhamad c0fafd3f7c feat: add cron job for allowance and implement cron lock 2024-12-30 16:10:19 +03:00
abdalhamid99 f7290419d2 Merge pull request #25 from HamzaSha1/feat/add-loggers
feat: add loggers to all services
2024-12-30 10:44:07 +03:00
Abdalhamid Alhamad 0fd2066c4a feat: add loggers to all services 2024-12-30 10:35:36 +03:00
abdalhamid99 cb54311a7b Merge pull request #24 from HamzaSha1/refactor/seperate-auth-and-user
refactor: seperate user and auth modules
2024-12-29 14:30:15 +03:00
Abdalhamid Alhamad ca71632755 refactor: sepeare user and auth modules 2024-12-29 14:17:39 +03:00
abdalhamid99 ebf335eabd Merge pull request #23 from HamzaSha1/feat/use-event-emitter
feat: handle notification async using event emitter
2024-12-29 11:53:23 +03:00
Abdalhamid Alhamad f383f6d14d feat: handle notification async using event emitter 2024-12-29 11:44:51 +03:00
abdalhamid99 5663a287f9 Merge pull request #22 from HamzaSha1/feat/mobile-notification
feat: sms using twillio
2024-12-29 10:00:24 +03:00
Abdalhamid Alhamad a7028fa64c feat: sms using twillio 2024-12-29 09:46:02 +03:00
abdalhamid99 0750509a85 Merge pull request #21 from HamzaSha1/feat/notifications
Feat/notifications
2024-12-24 13:08:29 +03:00
Abdalhamid Alhamad 4d9ebe729e Merge branch 'mvp-1' of github.com:HamzaSha1/zod-backend into feat/notifications 2024-12-24 13:00:58 +03:00
Abdalhamid Alhamad bb8cc33d53 fix: register fcm token on enabling push notification 2024-12-24 13:00:54 +03:00
abdalhamid99 e933cacdcf Merge pull request #20 from HamzaSha1/feat/notifications
feat: working on push notifications journey
2024-12-24 12:18:32 +03:00
Abdalhamid Alhamad 3719498c2f feat: working on push notifications journey 2024-12-24 12:10:49 +03:00
abdalhamid99 c7470302bd Merge pull request #19 from HamzaSha1/feat/gifts-journey
feat: gift journeys
2024-12-22 16:47:01 +03:00
Abdalhamid Alhamad 5e9e83cb74 feat: gift journeys 2024-12-22 16:34:02 +03:00
abdalhamid99 4cef58580e Merge pull request #18 from HamzaSha1/feat/logout
Feat/logout
2024-12-22 10:55:24 +03:00
Abdalhamid Alhamad 0ba09cbf8b Merge branch 'mvp-1' of github.com:HamzaSha1/zod-backend into feat/logout 2024-12-22 10:48:03 +03:00
Abdalhamid Alhamad 28a2cb5d75 fix: fix exception in case of logout 2024-12-22 10:47:59 +03:00
abdalhamid99 4961a192ea Merge pull request #17 from HamzaSha1/feat/logout
Feat/logout
2024-12-19 16:59:16 +03:00
Abdalhamid Alhamad 8ab47f3835 Merge branch 'mvp-1' of github.com:HamzaSha1/zod-backend into feat/logout 2024-12-19 16:50:44 +03:00
Abdalhamid Alhamad 8112fb81a2 feat: logout 2024-12-19 16:50:39 +03:00
abdalhamid99 2c3c862c4a Merge pull request #16 from HamzaSha1/feat/refresh-token
feat: refresh token
2024-12-19 16:49:25 +03:00
Abdalhamid Alhamad 93f5d83825 feat: refresh token 2024-12-19 16:25:39 +03:00
abdalhamid99 ea60ac3d7b Merge pull request #15 from HamzaSha1/feat/allowance-journey
feat: allowance journey
2024-12-19 15:27:00 +03:00
Abdalhamid Alhamad 0748695f23 fix: remove comments from allowance entity 2024-12-19 15:21:44 +03:00
Abdalhamid Alhamad a201692c0c feat: allowance journey 2024-12-19 15:18:35 +03:00
abdalhamid99 fd6c1d1442 Merge pull request #14 from HamzaSha1/feat/money-requests
feat: working on money requests journrey
2024-12-18 13:17:29 +03:00
Abdalhamid Alhamad ed57ce6e91 feat: working on money requests jounrey 2024-12-18 12:57:23 +03:00
abdalhamid99 33453b193f Merge pull request #13 from HamzaSha1/feat/register-junior-via-qrcode
feat: onboard junior by qrcode
2024-12-15 17:00:42 +03:00
Abdalhamid Alhamad b0972f1a0a feat: onbard junior by qrcode 2024-12-15 16:46:49 +03:00
abdalhamid99 7437403756 Merge pull request #12 from HamzaSha1/feat/saving-goals
feat: working on saving goals journey for juniors
2024-12-15 12:53:27 +03:00
Abdalhamid Alhamad 4d2f6f57f4 feat: working on saving goals jounrey for juniors 2024-12-15 12:44:59 +03:00
Abdalhamid Alhamad 24d990592d fix: fix tasks submission journey 2024-12-12 15:22:04 +03:00
Abdalhamid Alhamad 5b7b7ff689 fix: fix oci bucket name in generating signed url 2024-12-12 13:30:11 +03:00
abdalhamid99 6fccacd085 Merge pull request #11 from HamzaSha1/feat/customer-settings
feat: update customer profile picture and notifications settings
2024-12-12 13:23:28 +03:00
Abdalhamid Alhamad 51fa61dbc6 feat: update customer profile picture and notifications settings 2024-12-12 13:15:47 +03:00
abdalhamid99 4867a5f858 Merge pull request #10 from HamzaSha1/feat/tasks-default-logo
feat: seed default task logos
2024-12-12 11:22:20 +03:00
Abdalhamid Alhamad 687b6a5c6d feat: seed default task logos 2024-12-12 11:14:38 +03:00
abdalhamid99 e6ed1772f7 Merge pull request #9 from HamzaSha1/feat/signed-urls
Feat/signed urls
2024-12-12 10:01:52 +03:00
Abdalhamid Alhamad 1f0a14fee4 fix: fix magic number lint issue 2024-12-12 09:47:49 +03:00
Abdalhamid Alhamad eb70828ae0 Merge branch 'mvp-1' of github.com:HamzaSha1/zod-backend into feat/signed-urls 2024-12-12 09:46:48 +03:00
Abdalhamid Alhamad 220a03cc46 feat: working on signed url for private files 2024-12-12 09:46:38 +03:00
Abdalhamid Alhamad 39b1e76bb5 fix: import tasks migration 2024-12-11 17:49:15 +03:00
abdalhamid99 83fc634d25 Merge pull request #8 from HamzaSha1/feat/tasks
feat: tasks journey
2024-12-11 11:14:21 +03:00
Abdalhamid Alhamad 35b434bc3d fix: fix multiple submissions 2024-12-11 11:09:55 +03:00
Abdalhamid Alhamad 749ee5457f feat: tasks jounrey 2024-12-11 10:27:51 +03:00
abdalhamid99 d539073f29 Merge pull request #7 from HamzaSha1/feat/roles-guard
feat: protecting endpoints by roles
2024-12-10 10:19:32 +03:00
Abdalhamid Alhamad 66e1bb0f28 feat: protecting endpoint by roles 2024-12-10 10:11:47 +03:00
abdalhamid99 577f91b796 Merge pull request #6 from HamzaSha1/feat/junior-theme
feat: set theme for junior users
2024-12-10 09:30:42 +03:00
Abdalhamid Alhamad 7ed37c30e1 feat: set theme for junior users 2024-12-10 09:23:30 +03:00
abdalhamid99 c2f63ccc72 Merge pull request #5 from HamzaSha1/feat/create-juniors
feat: create junior
2024-12-09 13:18:03 +03:00
Abdalhamid Alhamad 970a41c895 feat: create junior 2024-12-09 13:11:18 +03:00
abdalhamid99 3fd29b3905 Merge pull request #4 from HamzaSha1/feat/forget-password
feat:forget password
2024-12-08 13:26:18 +03:00
Abdalhamid Alhamad 7f7fef3f89 fix: fix pipeline 2024-12-08 13:21:46 +03:00
Abdalhamid Alhamad 90ee8023e6 feat:forget password 2024-12-08 13:15:38 +03:00
abdalhamid99 c486d558ad Merge pull request #3 from HamzaSha1/feat/user-registration
Feat/user registration
2024-12-05 11:50:36 +03:00
Abdalhamid Alhamad 85569af770 Merge branch 'mvp-1' of github.com:HamzaSha1/zod-backend into feat/user-registration 2024-12-05 11:43:21 +03:00
Abdalhamid Alhamad f97bb08c5c fix: handle duplicate emails 2024-12-05 11:42:56 +03:00
abdalhamid99 6f6e3f7e7b Merge pull request #2 from HamzaSha1/feat/user-registration
feat: registration journrey for parents
2024-12-05 11:34:58 +03:00
Abdalhamid Alhamad 26b2d153fd fix: fix unit test 2024-12-05 11:27:36 +03:00
Abdalhamid Alhamad 2577f2dcac feat: registration jounrey for parents 2024-12-05 11:20:50 +03:00
abdalhamid99 e4b69a406f Merge pull request #1 from HamzaSha1/feat/upload-files-to-oci-bucket
Feat/upload files to oci bucket
2024-12-02 12:27:16 +03:00
Abdalhamid Alhamad 2fdce34ffc fix: fix linting issues 2024-12-02 12:23:03 +03:00
Abdalhamid Alhamad 2bf9dea771 fix: remove access type from document 2024-12-02 12:13:03 +03:00
Abdalhamid Alhamad 847c078735 feat: upload document to oci bucket 2024-12-02 12:07:10 +03:00
abdalhamid99 14e70a63b8 Update README.md 2024-11-21 09:12:27 +03:00
Oracle Public Cloud User 05872b5170 feat:mvp1 initial commit 2024-11-21 06:07:08 +00:00
620 changed files with 42942 additions and 6226 deletions
+40
View File
@@ -0,0 +1,40 @@
NODE_ENV=dev
PORT=5000
DB_HOST=
DB_PASS=
DB_USER=postgres
DB_PORT=5432
DB_NAME=
MIGRATIONS_RUN=true
SWAGGER_API_DOCS_PATH="/api-docs"
JWT_ACCESS_TOKEN_SECRET=your_access_token_secret
JWT_ACCESS_TOKEN_EXPIRY=1d
JWT_REFRESH_TOKEN_SECRET=your_refresh_token_secret
JWT_REFRESH_TOKEN_EXPIRY=1d
USE_MOCK=true
OCI_TENANCY_ID=
OCI_USER_ID=
OCI_FINGERPRINT=
OCI_PRIVATE_KEY=
OCI_PASSPHRASE=""
OCI_NAMESPACE=
OCI_REGION=
MAIL_HOST=smtp.gmail.com
MAIL_USER=[email protected]
MAIL_PASSWORD=
MAIL_PORT=587
MAIL_FROM=UBA
BRANCH_IO_URL=https://api2.branch.io/v1/url
BRANCH_IO_KEY=
ZOD_BASE_URL=http://localhost:5001
ANDROID_PACKAGE_NAME=com.zod
IOS_PACKAGE_NAME=com.zod
ANDRIOD_JUNIOR_DEEPLINK_PATH=zodbank://juniors/qr-code/validate
IOS_JUNIOR_DEEPLINK_PATH=zodbank://juniors/qr-code/validate
+47
View File
@@ -0,0 +1,47 @@
/** @type {import('eslint').Linter.Config} */
module.exports = {
parser: '@typescript-eslint/parser',
parserOptions: {
project: 'tsconfig.json',
tsconfigRootDir: __dirname,
sourceType: 'module',
},
plugins: ['@typescript-eslint/eslint-plugin'],
extends: ['plugin:@typescript-eslint/recommended', 'plugin:prettier/recommended', 'plugin:security/recommended'],
root: true,
env: {
node: true,
jest: true,
},
ignorePatterns: ['.eslintrc.js', 'node_modules', 'dist', 'coverage', 'jest*\\.ts', 'typeorm*\\.ts'],
rules: {
'@typescript-eslint/interface-name-prefix': 'off',
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-unused-vars': [
'warn',
{ argsIgnorePattern: '^_', varsIgnorePattern: '^_', caughtErrorsIgnorePattern: '^_' },
],
'@typescript-eslint/no-inferrable-types': 'off',
'func-names': ['error', 'as-needed'],
'no-underscore-dangle': ['error'],
'require-await': ['error'],
'no-console': ['error'],
'no-multi-assign': ['error'],
'no-magic-numbers': ['error', { ignoreArrayIndexes: true}],
'no-multiple-empty-lines': ['error', { max: 1, maxEOF: 1, maxBOF: 0 }],
'max-len': [
'error',
{ code: 120, tabWidth: 2, ignoreUrls: true, ignoreStrings: true, ignoreTemplateLiterals: true },
],
},
overrides: [
{
files: ['*spec.ts'],
rules: {
'no-magic-numbers': ['off'],
},
},
],
};
+85
View File
@@ -0,0 +1,85 @@
name: CI Zod
on:
pull_request:
branches:
- '*'
push:
branches:
- dev
- main
env:
NODE_ENV: development
TAG: dev
jobs:
install_dependencies:
name: Install Dependencies
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@v3
- name: Set up Node.js
uses: actions/setup-node@v3
with:
node-version: 22.4.1
- name: Install Dependencies
run: npm install
- name: Cache Node Modules
uses: actions/cache@v3
with:
path: node_modules
key: ${{ runner.os }}-node_modules-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node_modules-
run_tests:
name: Run Tests
runs-on: ubuntu-latest
needs: install_dependencies
steps:
- name: Checkout Code
uses: actions/checkout@v3
- name: Set up Node.js
uses: actions/setup-node@v3
with:
node-version: 22.4.1
- name: Install Dependencies
run: npm install
- name: Run Lint
run: npm run lint
- name: Run Tests with Coverage
run: npm run test:cov
build_app:
name: Build Application
runs-on: ubuntu-latest
needs: run_tests
steps:
- name: Checkout Code
uses: actions/checkout@v3
- name: Set up Node.js
uses: actions/setup-node@v3
with:
node-version: 22.4.1
- name: Install Dependencies
run: npm install
- name: Build Application
run: npm run build
- name: Upload Build Artifacts
uses: actions/upload-artifact@v3
with:
name: dist
path: dist/
+56 -23
View File
@@ -1,24 +1,57 @@
/static
/media
.idea/
*.pyc
media/
*.name
*.iml
*.log
*.xml
*.pyo
.DS_Store
.idea
venv/*
static/*
*.pem
*.sqlite3
/migrations/__pycache__/
/__pycache__/
/*.pyc
*/__pycache__/*.pyc
__pycache__/
*.env
ve/*
# compiled output
/dist
/node_modules
/build
# Logs
logs
*.log
npm-debug.log*
pnpm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
# OS
.DS_Store
# Tests
/coverage
/.nyc_output
# IDEs and editors
/.idea
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace
# IDE - VSCode
.vscode/*
!.vscode/settings.json
# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local
# temp directory
.temp
.tmp
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
zod-certs
+6
View File
@@ -0,0 +1,6 @@
{
"singleQuote": true,
"trailingComma": "all",
"tabWidth": 2,
"printWidth": 120
}
+6
View File
@@ -0,0 +1,6 @@
{
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.organizeImports": "explicit"
}
}
+291
View File
@@ -0,0 +1,291 @@
# Allowance Scheduling System (Backend)
This document captures the complete allowance scheduling feature as implemented in the backend.
It is intended as a long-term reference for how the system works, why it was built this way,
and how to operate/extend it safely.
## Goals and Scope
- Allow parents/guardians to create a recurring allowance schedule for a child.
- Credit the allowance automatically based on the schedule.
- Scale safely with multiple workers and avoid duplicate credits.
- Keep cron lightweight and offload work to RabbitMQ workers.
Non-goals in the current implementation:
- UI changes
- Analytics/reporting views
- Advanced scheduling (e.g., custom weekdays)
## High-Level Flow
1. Parent creates a schedule via API.
2. Cron runs every 5 minutes and enqueues due schedules into RabbitMQ.
3. Workers consume queue messages, credit the allowance, and update the next run.
4. Idempotency is enforced in the database to prevent duplicate credits.
## Data Model
### Table: `allowance_schedules`
Purpose: Stores the schedule definition (amount, frequency, status, next run).
Key fields:
- `guardian_id`, `junior_id`: who funds and who receives.
- `amount`: the allowance amount.
- `frequency`: DAILY / WEEKLY / MONTHLY.
- `status`: ON / OFF.
- `next_run_at`, `last_run_at`: scheduling metadata.
Constraints:
- Unique `(guardian_id, junior_id)` ensures one schedule per child.
Entity: `src/allowance/entities/allowance-schedule.entity.ts`
### Table: `allowance_credits`
Purpose: Audit log and idempotency guard for each credit run.
Key fields:
- `schedule_id`: which schedule executed.
- `transaction_id`: the resulting transaction (nullable).
- `amount`, `run_at`, `credited_at`.
Idempotency:
- Unique `(schedule_id, run_at)` prevents duplicates even with multiple workers.
Entity: `src/allowance/entities/allowance-credit.entity.ts`
### Table: `cron_runs`
Purpose: shared audit table for all cron jobs (not just allowance).
Key fields:
- `job_name`: unique identifier for the cron.
- `status`: SUCCESS / FAILED.
- `processed_count`: number of schedules processed.
- `error_message`: failure reason.
- `started_at`, `finished_at`.
Entity: `src/cron/entities/cron-run.entity.ts`
## API (Schedule Creation)
Endpoint:
- `POST /guardians/me/allowances/:juniorId`
Input DTO:
- `amount` (required, numeric, positive)
- `frequency` (required, enum)
- `status` (required, enum)
DTO: `src/allowance/dtos/request/create-allowance-schedule.request.dto.ts`
Response DTO:
- `AllowanceScheduleResponseDto` with schedule fields.
DTO: `src/allowance/dtos/response/allowance-schedule.response.dto.ts`
Business validation:
- Child must belong to guardian.
- No duplicate schedule for the same guardian+child.
Service: `src/allowance/services/allowance.service.ts`
## Cron Producer (Queue Enqueue)
Cron job:
- Runs every 5 minutes.
- Batches schedules (cursor-based) to avoid large load.
- Enqueues each schedule to RabbitMQ.
Cron: `src/cron/tasks/allowance-schedule.cron.ts`
Batch behavior:
- Batch size = 100
- Uses a cursor (`nextRunAt`, `id`) for stable pagination.
- Prevents re-reading the same rows.
Locking:
- `BaseCronService` uses cache lock to ensure only one instance runs.
- Each cron run is logged to `cron_runs` with status and processed count.
Lock: `src/cron/services/base-cron.service.ts`
## Queue Publisher
Queue publisher:
- Asserts queue + retry/DLQ exchanges.
- Enqueues jobs with message id (for traceability).
Service: `src/allowance/services/allowance-queue.service.ts`
Queue names:
- `allowance.schedule` (main)
- `allowance.schedule.retry` (retry with TTL)
- `allowance.schedule.dlq` (dead-letter queue)
Constants: `src/allowance/constants/allowance-queue.constants.ts`
## Worker Consumer (Transfers)
Worker:
- Consumes `allowance.schedule` queue.
- Validates schedule is due and active.
- Creates `allowance_credits` record for idempotency.
- Transfers money via `cardService.transferToChild`.
- Updates `last_run_at` and `next_run_at`.
Worker: `src/allowance/services/allowance-worker.service.ts`
Transfer:
- Uses existing logic in `card.service.ts` for balance updates and transaction creation.
Service: `src/card/services/card.service.ts`
### Idempotency details
1. Worker inserts `allowance_credits` row first.
2. Unique constraint blocks duplicates.
3. If transfer fails, the credit row is removed so the job can retry.
This makes multiple workers safe.
## Retry + DLQ Strategy
Retry delay:
- Failed jobs are dead-lettered to retry queue.
- Retry queue has `messageTtl = 10 minutes`.
- After TTL, job is routed back to main queue.
DLQ:
- If a job fails `ALLOWANCE_MAX_RETRIES` times (default 5), it is routed to the DLQ.
- This prevents endless loops and allows manual inspection.
Config:
- `ALLOWANCE_MAX_RETRIES` (default 5)
## Redis Usage
Redis is used by `BaseCronService` to enforce a **distributed lock** for the cron job.
This prevents multiple backend instances from enqueuing the same schedules at the same time.
Lock behavior:
- If the lock key exists, cron exits early.
- If the lock key is absent, cron sets it with a TTL and proceeds.
- TTL ensures the lock is released even if a node crashes.
Service: `src/cron/services/base-cron.service.ts`
## RabbitMQ Setup and Behavior
The allowance system uses RabbitMQ for asynchronous processing. Cron publishes due schedules,
and workers consume them.
### Exchanges and Queues
Main queue:
- `allowance.schedule`
Retry setup:
- Exchange: `allowance.schedule.retry.exchange`
- Queue: `allowance.schedule.retry`
- Binding key: `allowance.schedule`
- TTL: 10 minutes
- Dead-letter route: back to `allowance.schedule`
Dead-letter queue (DLQ):
- Exchange: `allowance.schedule.dlq.exchange`
- Queue: `allowance.schedule.dlq`
- Binding key: `allowance.schedule`
### Flow Summary
1. Cron enqueues jobs to `allowance.schedule`.
2. Worker consumes jobs from `allowance.schedule`.
3. On failure, job is **dead-lettered** to `allowance.schedule.retry`.
4. After 10 minutes, it returns to `allowance.schedule`.
5. After max retries, worker publishes the job to `allowance.schedule.dlq`.
### Where it is configured
- Publisher setup: `src/allowance/services/allowance-queue.service.ts`
- Worker consumer: `src/allowance/services/allowance-worker.service.ts`
- Queue constants: `src/allowance/constants/allowance-queue.constants.ts`
## Environment Variables
- `RABBITMQ_URL` (required for queue/worker)
- `ALLOWANCE_QUEUE_NAME` (optional, defaults to `allowance.schedule`)
- `ALLOWANCE_MAX_RETRIES` (optional, defaults to 5)
- `ALLOWANCE_RETRY_DELAY_MS` (optional, defaults to 10 minutes)
### Example .env snippet
```
RABBITMQ_URL=amqp://guest:guest@localhost:5672
ALLOWANCE_QUEUE_NAME=allowance.schedule
ALLOWANCE_MAX_RETRIES=5
ALLOWANCE_RETRY_DELAY_MS=600000
```
## Operational Checklist
- Ensure Redis is running (cron locking).
- Ensure RabbitMQ is running (queue + workers).
- Start at least one worker process.
- Monitor DLQ for failures.
## Manual Test Checklist
1. Create schedule:
- POST `/guardians/me/allowances/:juniorId`
- Valid amount, frequency, status.
2. Duplicate schedule:
- Expect `ALLOWANCE.ALREADY_EXISTS`.
3. Cron enqueue:
- Wait for cron interval or manually trigger.
- Confirm messages appear in RabbitMQ.
4. Worker:
- Ensure worker is running.
- Verify transfers happen and `allowance_credits` is created.
5. Failure paths:
- Simulate transfer failure and verify retry queue behavior.
- Confirm DLQ after max retries.
## Operational Notes
- For large volumes, scale workers horizontally.
- Keep cron lightweight; do not perform transfers in cron.
- Monitor queue depth and DLQ entries.
## Known Limitations (Current)
- Only one schedule per child (guardian+junior unique).
- No custom weekdays or complex schedules.
- Retry delay is fixed at 10 minutes (can be configurable).
## File Map (Quick Reference)
- API:
- `src/allowance/controllers/allowance.controller.ts`
- `src/allowance/services/allowance.service.ts`
- `src/allowance/dtos/request/create-allowance-schedule.request.dto.ts`
- `src/allowance/dtos/response/allowance-schedule.response.dto.ts`
- Cron:
- `src/cron/tasks/allowance-schedule.cron.ts`
- `src/cron/services/base-cron.service.ts`
- Queue/Worker:
- `src/allowance/services/allowance-queue.service.ts`
- `src/allowance/services/allowance-worker.service.ts`
- `src/allowance/constants/allowance-queue.constants.ts`
- Repositories:
- `src/allowance/repositories/allowance-schedule.repository.ts`
- `src/allowance/repositories/allowance-credit.repository.ts`
- Entities:
- `src/allowance/entities/allowance-schedule.entity.ts`
- `src/allowance/entities/allowance-credit.entity.ts`
-11
View File
@@ -1,11 +0,0 @@
FROM python:3.9
ENV PYTHONUNBUFFERED 1
RUN mkdir /usr/src/app
WORKDIR /usr/src/app
COPY . .
RUN apt-get update
RUN apt-get install wkhtmltopdf -y
RUN apt install -y gdal-bin python3-gdal
RUN pip install -r requirements.txt
WORKDIR /usr/src/app
+33
View File
@@ -0,0 +1,33 @@
# Base image
FROM node:22.4.1-alpine
RUN apk update && \
apk upgrade && \
apk add ca-certificates curl && \
update-ca-certificates
#saudi timezone
ENV TZ=Asia/Riyadh
# Create app directory
RUN mkdir /app && chown node:node /app
WORKDIR /app
# finally switch user to node
USER node
# A wildcard is used to ensure both package.json, package-lock.json and package-lock.json are copied
COPY --chown=node:node package*.json ./
# Install app dependencies
RUN npm install --frozen-lockfile
# Bundle app source
COPY --chown=node:node . .
# Creates a "dist" folder with the production build
RUN npm run build
# Start the server using the production build
CMD [ "node", "dist/main" ]
+99 -1
View File
@@ -1 +1,99 @@
# ZODBank-Backend
<p align="center">
<a href="http://nestjs.com/" target="blank"><img src="https://nestjs.com/img/logo-small.svg" width="120" alt="Nest Logo" /></a>
</p>
[circleci-image]: https://img.shields.io/circleci/build/github/nestjs/nest/master?token=abc123def456
[circleci-url]: https://circleci.com/gh/nestjs/nest
<p align="center">A progressive <a href="http://nodejs.org" target="_blank">Node.js</a> framework for building efficient and scalable server-side applications.</p>
<p align="center">
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/v/@nestjs/core.svg" alt="NPM Version" /></a>
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/l/@nestjs/core.svg" alt="Package License" /></a>
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/dm/@nestjs/common.svg" alt="NPM Downloads" /></a>
<a href="https://circleci.com/gh/nestjs/nest" target="_blank"><img src="https://img.shields.io/circleci/build/github/nestjs/nest/master" alt="CircleCI" /></a>
<a href="https://coveralls.io/github/nestjs/nest?branch=master" target="_blank"><img src="https://coveralls.io/repos/github/nestjs/nest/badge.svg?branch=master#9" alt="Coverage" /></a>
<a href="https://discord.gg/G7Qnnhy" target="_blank"><img src="https://img.shields.io/badge/discord-online-brightgreen.svg" alt="Discord"/></a>
<a href="https://opencollective.com/nest#backer" target="_blank"><img src="https://opencollective.com/nest/backers/badge.svg" alt="Backers on Open Collective" /></a>
<a href="https://opencollective.com/nest#sponsor" target="_blank"><img src="https://opencollective.com/nest/sponsors/badge.svg" alt="Sponsors on Open Collective" /></a>
<a href="https://paypal.me/kamilmysliwiec" target="_blank"><img src="https://img.shields.io/badge/Donate-PayPal-ff3f59.svg" alt="Donate us"/></a>
<a href="https://opencollective.com/nest#sponsor" target="_blank"><img src="https://img.shields.io/badge/Support%20us-Open%20Collective-41B883.svg" alt="Support us"></a>
<a href="https://twitter.com/nestframework" target="_blank"><img src="https://img.shields.io/twitter/follow/nestframework.svg?style=social&label=Follow" alt="Follow us on Twitter"></a>
</p>
<!--[![Backers on Open Collective](https://opencollective.com/nest/backers/badge.svg)](https://opencollective.com/nest#backer)
[![Sponsors on Open Collective](https://opencollective.com/nest/sponsors/badge.svg)](https://opencollective.com/nest#sponsor)-->
## Description
[Nest](https://github.com/nestjs/nest) framework TypeScript starter repository.
## Project setup
```bash
$ npm install
```
## Compile and run the project
```bash
# development
$ npm run start
# watch mode
$ npm run start:dev
# production mode
$ npm run start:prod
```
## Run tests
```bash
# unit tests
$ npm run test
# e2e tests
$ npm run test:e2e
# test coverage
$ npm run test:cov
```
## Deployment
When you're ready to deploy your NestJS application to production, there are some key steps you can take to ensure it runs as efficiently as possible. Check out the [deployment documentation](https://docs.nestjs.com/deployment) for more information.
If you are looking for a cloud-based platform to deploy your NestJS application, check out [Mau](https://mau.nestjs.com), our official platform for deploying NestJS applications on AWS. Mau makes deployment straightforward and fast, requiring just a few simple steps:
```bash
$ npm install -g mau
$ mau deploy
```
With Mau, you can deploy your application in just a few clicks, allowing you to focus on building features rather than managing infrastructure.
## Resources
Check out a few resources that may come in handy when working with NestJS:
- Visit the [NestJS Documentation](https://docs.nestjs.com) to learn more about the framework.
- For questions and support, please visit our [Discord channel](https://discord.gg/G7Qnnhy).
- To dive deeper and get more hands-on experience, check out our official video [courses](https://courses.nestjs.com/).
- Deploy your application to AWS with the help of [NestJS Mau](https://mau.nestjs.com) in just a few clicks.
- Visualize your application graph and interact with the NestJS application in real-time using [NestJS Devtools](https://devtools.nestjs.com).
- Need help with your project (part-time to full-time)? Check out our official [enterprise support](https://enterprise.nestjs.com).
- To stay in the loop and get updates, follow us on [X](https://x.com/nestframework) and [LinkedIn](https://linkedin.com/company/nestjs).
- Looking for a job, or have a job to offer? Check out our official [Jobs board](https://jobs.nestjs.com).
## Support
Nest is an MIT-licensed open source project. It can grow thanks to the sponsors and support by the amazing backers. If you'd like to join them, please [read more here](https://docs.nestjs.com/support).
## Stay in touch
- Author - [Kamil Myśliwiec](https://twitter.com/kammysliwiec)
- Website - [https://nestjs.com](https://nestjs.com/)
- Twitter - [@nestframework](https://twitter.com/nestframework)
## License
Nest is [MIT licensed](https://github.com/nestjs/nest/blob/master/LICENSE).
-49
View File
@@ -1,49 +0,0 @@
"""Account admin"""
from django.contrib import admin
"""Import django app"""
from .models import UserEmailOtp, DefaultTaskImages, UserNotification, UserDelete, UserDeviceDetails
# Register your models here.
@admin.register(UserDelete)
class UserDeleteAdmin(admin.ModelAdmin):
"""User profile admin"""
list_display = ['user', 'old_email', 'd_email']
def __str__(self):
"""Return delete user"""
return self.user
@admin.register(UserNotification)
class UserNotificationAdmin(admin.ModelAdmin):
"""User profile admin"""
list_display = ['user', 'push_notification', 'email_notification', 'sms_notification']
def __str__(self):
"""Return image url"""
return self.image_url
@admin.register(DefaultTaskImages)
class DefaultTaskImagesAdmin(admin.ModelAdmin):
"""User profile admin"""
list_display = ['task_name', 'image_url']
def __str__(self):
"""Return image url"""
return self.image_url
@admin.register(UserEmailOtp)
class UserEmailOtpAdmin(admin.ModelAdmin):
"""User Email otp admin"""
list_display = ['email']
def __str__(self):
"""Return object in email and otp format"""
return self.email + '-' + self.otp
@admin.register(UserDeviceDetails)
class UserDeviceDetailsAdmin(admin.ModelAdmin):
"""User profile admin"""
list_display = ['user', 'device_id']
def __str__(self):
"""Return user email"""
return self.user.email
-9
View File
@@ -1,9 +0,0 @@
"""Account app file"""
"""Import Django"""
from django.apps import AppConfig
class AccountConfig(AppConfig):
"""default configurations"""
default_auto_field = 'django.db.models.BigAutoField'
name = 'account'
-40
View File
@@ -1,40 +0,0 @@
"""middleware file"""
"""Django import"""
from rest_framework import status
from rest_framework.response import Response
from rest_framework.renderers import JSONRenderer
"""App django"""
from account.utils import custom_error_response
from account.models import UserDeviceDetails
from base.messages import ERROR_CODE, SUCCESS_CODE
# Custom middleware
# when user login with
# multiple device simultaneously
# It restricted login in
# multiple devices only
# user can login in single
# device at a time"""
class CustomMiddleware(object):
"""Custom middleware"""
def __init__(self, get_response):
"""response"""
self.get_response = get_response
def __call__(self, request):
# Code to be executed before the view is called
response = self.get_response(request)
# Code to be executed after the view is called
device_id = request.META.get('HTTP_DEVICE_ID')
if request.user.is_authenticated:
"""device details"""
device_details = UserDeviceDetails.objects.filter(user=request.user, device_id=device_id).last()
if device_id and not device_details:
custom_error = custom_error_response(ERROR_CODE['2037'], response_status=status.HTTP_404_NOT_FOUND)
response = Response(custom_error.data, status=status.HTTP_404_NOT_FOUND)
# Set content type header to "application/json"
response['Content-Type'] = 'application/json'
# Render the response as JSON
renderer = JSONRenderer()
response.content = renderer.render(response.data)
return response
-65
View File
@@ -1,65 +0,0 @@
# Generated by Django 4.2.2 on 2023-06-23 12:05
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
initial = True
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='UserEmailOtp',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('email', models.EmailField(max_length=254)),
('otp', models.CharField(max_length=10)),
('is_verified', models.BooleanField(default=False)),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('expired_at', models.DateTimeField(blank=True, null=True)),
('is_active', models.BooleanField(default=True)),
],
options={
'db_table': 'user_email_otp',
},
),
migrations.CreateModel(
name='UserPhoneOtp',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('country_code', models.IntegerField()),
('phone', models.CharField(max_length=17)),
('otp', models.CharField(max_length=10)),
('is_verified', models.BooleanField(default=False)),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('expired_at', models.DateTimeField(blank=True, null=True)),
('is_active', models.BooleanField(default=True)),
],
options={
'db_table': 'user_phone_otp',
},
),
migrations.CreateModel(
name='UserProfile',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('user_type', models.CharField(blank=True, choices=[('1', 'junior'), ('2', 'guardian'), ('3', 'superuser')], default=None, max_length=15, null=True)),
('is_verified', models.BooleanField(default=False)),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('is_active', models.BooleanField(default=False)),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='user_profile', to=settings.AUTH_USER_MODEL)),
],
options={
'db_table': 'user_profile',
},
),
]
@@ -1,18 +0,0 @@
# Generated by Django 4.2.2 on 2023-06-29 12:10
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('account', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='useremailotp',
name='user_type',
field=models.CharField(blank=True, choices=[('1', 'junior'), ('2', 'guardian'), ('3', 'superuser')], default=None, max_length=15, null=True),
),
]
@@ -1,26 +0,0 @@
# Generated by Django 4.2.2 on 2023-07-07 10:23
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('account', '0002_useremailotp_user_type'),
]
operations = [
migrations.CreateModel(
name='DefaultTaskImages',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('task_name', models.CharField(max_length=15)),
('image_url', models.URLField(blank=True, default=None, null=True)),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
],
options={
'db_table': 'default_task_image',
},
),
]
-31
View File
@@ -1,31 +0,0 @@
# Generated by Django 4.2.2 on 2023-07-10 09:24
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('account', '0003_defaulttaskimages'),
]
operations = [
migrations.CreateModel(
name='UserDelete',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('old_email', models.EmailField(blank=True, max_length=254, null=True, verbose_name='Original Email')),
('d_email', models.EmailField(blank=True, max_length=254, null=True, verbose_name='Dummy Email')),
('is_active', models.BooleanField(default=True)),
('reason', models.TextField(blank=True, max_length=500, null=True, verbose_name='Reason for Leaving')),
('created_at', models.DateTimeField(auto_now_add=True)),
('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='delete_information_set', to=settings.AUTH_USER_MODEL)),
],
options={
'db_table': 'user_delete_information',
},
),
]
@@ -1,31 +0,0 @@
# Generated by Django 4.2.2 on 2023-07-10 12:40
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('account', '0004_userdelete'),
]
operations = [
migrations.CreateModel(
name='UserNotification',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('push_notification', models.BooleanField(default=True)),
('email_notification', models.BooleanField(default=True)),
('sms_notification', models.BooleanField(default=True)),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='user_notification', to=settings.AUTH_USER_MODEL)),
],
options={
'db_table': 'user_notification',
},
),
]
@@ -1,21 +0,0 @@
# Generated by Django 4.2.2 on 2023-07-11 11:26
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('account', '0005_usernotification'),
]
operations = [
migrations.AlterModelOptions(
name='useremailotp',
options={'verbose_name': 'User Email OTP', 'verbose_name_plural': 'User Email OTP'},
),
migrations.AlterModelOptions(
name='usernotification',
options={'verbose_name': 'User Notification', 'verbose_name_plural': 'User Notification'},
),
]
@@ -1,21 +0,0 @@
# Generated by Django 4.2.2 on 2023-07-14 09:34
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('account', '0006_alter_useremailotp_options_and_more'),
]
operations = [
migrations.AlterModelOptions(
name='defaulttaskimages',
options={'verbose_name': 'Default Task images', 'verbose_name_plural': 'Default Task images'},
),
migrations.AlterModelOptions(
name='userdelete',
options={'verbose_name': 'Deleted User', 'verbose_name_plural': 'Deleted User'},
),
]
@@ -1,31 +0,0 @@
# Generated by Django 4.2.2 on 2023-07-14 11:08
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('account', '0007_alter_defaulttaskimages_options_and_more'),
]
operations = [
migrations.CreateModel(
name='UserDeviceDetails',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('device_id', models.CharField(max_length=500)),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='user_device_details', to=settings.AUTH_USER_MODEL)),
],
options={
'verbose_name': 'User Device Details',
'verbose_name_plural': 'User Device Details',
'db_table': 'user_device_details',
},
),
]
-167
View File
@@ -1,167 +0,0 @@
"""Account module file"""
"""Django import"""
from django.db import models
from django.contrib.auth.models import User
"""App import"""
from base.constants import USER_TYPE
# Create your models here.
class UserProfile(models.Model):
"""
User details
"""
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='user_profile')
user_type = models.CharField(max_length=15, choices=USER_TYPE, null=True, blank=True, default=None)
is_verified = models.BooleanField(default=False)
# OTP validity
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
is_active = models.BooleanField(default=False)
class Meta(object):
""" Meta information """
db_table = 'user_profile'
def __str__(self):
"""return phone as an object"""
return f'{self.user}'
class UserPhoneOtp(models.Model):
"""
This class is used to verify user email and their contact no.
"""
"""user details"""
country_code = models.IntegerField()
phone = models.CharField(max_length=17)
"""otp details"""
otp = models.CharField(max_length=10)
is_verified = models.BooleanField(default=False)
# OTP validity
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
expired_at = models.DateTimeField(blank=True, null=True)
is_active = models.BooleanField(default=True)
class Meta(object):
""" Meta information """
db_table = 'user_phone_otp'
def __str__(self):
"""return phone as an object"""
return self.phone
class UserEmailOtp(models.Model):
"""
This class is used to verify user email and their contact no.
"""
"""user details"""
email = models.EmailField()
"""otp details"""
otp = models.CharField(max_length=10)
is_verified = models.BooleanField(default=False)
user_type = models.CharField(max_length=15, choices=USER_TYPE, null=True, blank=True, default=None)
# OTP validity
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
expired_at = models.DateTimeField(blank=True, null=True)
is_active = models.BooleanField(default=True)
class Meta(object):
""" Meta information """
db_table = 'user_email_otp'
verbose_name = 'User Email OTP'
verbose_name_plural = 'User Email OTP'
def __str__(self):
"""return phone as an object"""
return self.email
class DefaultTaskImages(models.Model):
"""Default images upload in oss bucket"""
task_name = models.CharField(max_length=15)
image_url = models.URLField(null=True, blank=True, default=None)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta(object):
""" Meta information """
db_table = 'default_task_image'
verbose_name = 'Default Task images'
verbose_name_plural = 'Default Task images'
def __str__(self):
"""return phone as an object"""
return self.task_name
class UserDelete(models.Model):
"""
User delete information
"""
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='delete_information_set')
"""Old email"""
old_email = models.EmailField(blank=True, null=True, verbose_name='Original Email')
"""Dummy email"""
d_email = models.EmailField(blank=True, null=True, verbose_name='Dummy Email')
is_active = models.BooleanField(default=True)
"""reason for leaving"""
reason = models.TextField(max_length=500, blank=True, null=True, verbose_name='Reason for Leaving')
created_at = models.DateTimeField(auto_now_add=True)
class Meta(object):
""" Meta information """
db_table = 'user_delete_information'
verbose_name = 'Deleted User'
verbose_name_plural = 'Deleted User'
def __str__(self):
return self.user.email
class UserNotification(models.Model):
"""
User notification details
"""
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='user_notification')
"""Push Notification"""
push_notification = models.BooleanField(default=True)
"""Email Notification"""
email_notification = models.BooleanField(default=True)
"""SMS Notification"""
sms_notification = models.BooleanField(default=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta(object):
""" Meta information """
db_table = 'user_notification'
verbose_name = 'User Notification'
verbose_name_plural = 'User Notification'
def __str__(self):
return self.user.email
class UserDeviceDetails(models.Model):
"""
User notification details
"""
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='user_device_details')
"""Device ID"""
device_id = models.CharField(max_length=500)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta(object):
""" Meta information """
db_table = 'user_device_details'
verbose_name = 'User Device Details'
verbose_name_plural = 'User Device Details'
def __str__(self):
return self.user.email
-340
View File
@@ -1,340 +0,0 @@
"""Account serializer"""
"""Django Import"""
# Import Refresh token of jwt
from rest_framework import serializers
from django.contrib.auth.models import User
from rest_framework_simplejwt.tokens import RefreshToken
import secrets
"""App import"""
# Import guardian's model,
# Import junior's model,
# Import account's model,
# Import constant from
# base package,
# Import messages from
# base package,
# Import some functions
# from utils file"""
from guardian.models import Guardian
from junior.models import Junior
from account.models import UserEmailOtp, DefaultTaskImages, UserDelete, UserNotification, UserPhoneOtp
from base.constants import GUARDIAN, JUNIOR, SUPERUSER, NUMBER
from base.messages import ERROR_CODE, SUCCESS_CODE, STATUS_CODE_ERROR
from .utils import delete_user_account_condition_social, delete_user_account_condition
# In this serializer file
# define google login serializer
# update junior profile,
# update guardian profile,
# super admin serializer,
# reset password,
# forgot password,
# change password,
# basic junior serializer,
# basic guardian serializer,
# user delete account serializer,
# user notification serializer,
# update user notification serializer,
# default task's images serializer,
# upload default task's images serializer,
# email verification serializer,
# phone otp serializer
# create all serializer here
class GoogleLoginSerializer(serializers.Serializer):
"""google login serializer"""
access_token = serializers.CharField(max_length=5000, required=True)
class Meta(object):
"""meta class"""
fields = ('access_token',)
class UpdateJuniorProfileImageSerializer(serializers.ModelSerializer):
"""update junior image"""
class Meta(object):
"""Meta info"""
model = Junior
fields = ['id', 'image']
def update(self, instance, validated_data):
"""update image """
junior_image = validated_data.get('image', instance.image)
instance.image = junior_image
instance.save()
return instance
class UpdateGuardianImageSerializer(serializers.ModelSerializer):
"""update guardian image"""
class Meta(object):
"""Meta info"""
model = Guardian
fields = ['id','image']
def update(self, instance, validated_data):
"""update image """
instance.image = validated_data.get('image', instance.image)
instance.save()
return instance
class ResetPasswordSerializer(serializers.Serializer):
"""Reset Password after verification"""
verification_code = serializers.CharField(max_length=10)
password = serializers.CharField(required=True)
class Meta(object):
"""Meta info"""
model = User
def create(self, validated_data):
verification_code = validated_data.pop('verification_code')
password = validated_data.pop('password')
# fetch email otp object of the user
user_opt_details = UserEmailOtp.objects.filter(otp=verification_code, is_verified=True).last()
if user_opt_details:
user_details = User.objects.filter(email=user_opt_details.email).last()
if user_details:
if user_details.check_password(password):
raise serializers.ValidationError({"details":ERROR_CODE['2001'],"code":"400", "status":"failed"})
user_details.set_password(password)
user_details.save()
return {'password':password}
return user_opt_details
return ''
class ChangePasswordSerializer(serializers.Serializer):
"""Update Password after verification"""
current_password = serializers.CharField(max_length=100)
new_password = serializers.CharField(required=True)
class Meta(object):
"""Meta info"""
model = User
def validate_current_password(self, value):
user = self.context
# check old password
if self.context.password not in ('', None) and user.check_password(value):
return value
raise serializers.ValidationError(ERROR_CODE['2015'])
def create(self, validated_data):
new_password = validated_data.pop('new_password')
current_password = validated_data.pop('current_password')
"""Check new password is different from current password"""
if new_password == current_password:
raise serializers.ValidationError({"details": ERROR_CODE['2026']})
user_details = User.objects.filter(email=self.context).last()
if user_details:
user_details.set_password(new_password)
user_details.save()
return {'password':new_password}
return ''
class ForgotPasswordSerializer(serializers.Serializer):
"""Forget password serializer"""
email = serializers.EmailField()
class SuperUserSerializer(serializers.ModelSerializer):
"""Super admin serializer"""
user_type = serializers.SerializerMethodField('get_user_type')
auth_token = serializers.SerializerMethodField('get_auth_token')
refresh_token = serializers.SerializerMethodField('get_refresh_token')
def get_auth_token(self, obj):
refresh = RefreshToken.for_user(obj.auth)
access_token = str(refresh.access_token)
return access_token
def get_refresh_token(self, obj):
refresh = RefreshToken.for_user(obj.user)
refresh_token = str(refresh)
return refresh_token
def get_user_type(self, obj):
"""user type"""
return str(NUMBER['three'])
class Meta(object):
"""Meta info"""
model = User
fields = ['id', 'auth_token', 'refresh_token', 'username', 'email', 'first_name',
'last_name', 'is_active', 'user_type']
class GuardianSerializer(serializers.ModelSerializer):
"""guardian serializer"""
user_type = serializers.SerializerMethodField('get_user_type')
email = serializers.SerializerMethodField('get_auth')
first_name = serializers.SerializerMethodField('get_first_name')
last_name = serializers.SerializerMethodField('get_last_name')
auth_token = serializers.SerializerMethodField('get_auth_token')
refresh_token = serializers.SerializerMethodField('get_refresh_token')
def get_auth_token(self, obj):
refresh = RefreshToken.for_user(obj.user)
access_token = str(refresh.access_token)
return access_token
def get_refresh_token(self, obj):
refresh = RefreshToken.for_user(obj.user)
refresh_token = str(refresh)
return refresh_token
def get_user_type(self, obj):
"""user type"""
email_verified = UserEmailOtp.objects.filter(email=obj.user.username).last()
if email_verified and email_verified.user_type is not None:
return email_verified.user_type
return str(NUMBER['two'])
def get_auth(self, obj):
"""user email address"""
return obj.user.username
def get_first_name(self, obj):
"""user first name"""
return obj.user.first_name
def get_last_name(self, obj):
"""user last name"""
return obj.user.last_name
class Meta(object):
"""Meta info"""
model = Guardian
fields = ['id', 'auth_token', 'refresh_token', 'email', 'first_name', 'last_name', 'country_code',
'phone', 'family_name', 'gender', 'dob', 'referral_code', 'is_active',
'is_complete_profile', 'passcode', 'image', 'created_at', 'updated_at', 'user_type', 'country_name']
class JuniorSerializer(serializers.ModelSerializer):
"""junior serializer"""
user_type = serializers.SerializerMethodField('get_user_type')
email = serializers.SerializerMethodField('get_auth')
first_name = serializers.SerializerMethodField('get_first_name')
last_name = serializers.SerializerMethodField('get_last_name')
auth_token = serializers.SerializerMethodField('get_auth_token')
refresh_token = serializers.SerializerMethodField('get_refresh_token')
def get_auth_token(self, obj):
refresh = RefreshToken.for_user(obj.auth)
access_token = str(refresh.access_token)
return access_token
def get_refresh_token(self, obj):
refresh = RefreshToken.for_user(obj.auth)
refresh_token = str(refresh)
return refresh_token
def get_user_type(self, obj):
email_verified = UserEmailOtp.objects.filter(email=obj.auth.username).last()
if email_verified and email_verified.user_type is not None:
return email_verified.user_type
return str(NUMBER['one'])
def get_auth(self, obj):
return obj.auth.username
def get_first_name(self, obj):
return obj.auth.first_name
def get_last_name(self, obj):
return obj.auth.last_name
class Meta(object):
"""Meta info"""
model = Junior
fields = ['id', 'auth_token', 'refresh_token', 'email', 'first_name', 'last_name', 'country_code',
'phone', 'gender', 'dob', 'guardian_code', 'referral_code','is_active', 'is_password_set',
'is_complete_profile', 'created_at', 'image', 'updated_at', 'user_type', 'country_name','is_invited']
class EmailVerificationSerializer(serializers.ModelSerializer):
"""Email verification serializer"""
class Meta(object):
"""Meta info"""
model = UserEmailOtp
fields = '__all__'
class DefaultTaskImagesSerializer(serializers.ModelSerializer):
"""Update Password after verification"""
class Meta(object):
"""Meta info"""
model = DefaultTaskImages
fields = ['id', 'task_name', 'image_url']
def create(self, validated_data):
# create default task object
data = DefaultTaskImages.objects.create(**validated_data)
return data
class DefaultTaskImagesDetailsSerializer(serializers.ModelSerializer):
"""Update Password after verification"""
class Meta(object):
"""Meta info"""
model = DefaultTaskImages
fields = '__all__'
class UserDeleteSerializer(serializers.ModelSerializer):
"""User Delete Serializer"""
class Meta(object):
"""Meta Information"""
model = UserDelete
fields = ['id','reason']
def create(self, validated_data):
user = self.context['user']
user_type = str(self.context['user_type'])
data = validated_data.get('reason')
passwd = self.context['password']
signup_method = self.context['signup_method']
random_num = secrets.randbelow(10001)
user_tb = User.objects.filter(id=user.id).last()
user_type_datas = UserEmailOtp.objects.filter(email=user.email).last()
# check password and sign up method
if user_tb and user_tb.check_password(passwd) and signup_method == str(NUMBER['one']):
user_type_data = user_type_datas.user_type
instance = delete_user_account_condition(user, user_type_data, user_type, user_tb, data, random_num)
return instance
elif user_tb and passwd is None and signup_method in ['2','3']:
inst = delete_user_account_condition_social(user, user_type, user_tb, data, random_num)
return inst
else:
raise serializers.ValidationError({"details": ERROR_CODE['2031'], "code": "400", "status": "failed"})
class UserNotificationSerializer(serializers.ModelSerializer):
"""User Notification serializer"""
class Meta(object):
"""Meta info"""
model = UserNotification
fields = '__all__'
class UpdateUserNotificationSerializer(serializers.ModelSerializer):
"""Update User Notification serializer"""
class Meta(object):
"""Meta info"""
model = UserNotification
fields = ['push_notification', 'email_notification', 'sms_notification']
def create(self, validated_data):
instance = UserNotification.objects.filter(user=self.context).last()
if instance:
# change notification status
instance.push_notification = validated_data.get('push_notification',instance.push_notification)
instance.email_notification = validated_data.get('email_notification', instance.email_notification)
instance.sms_notification = validated_data.get('sms_notification', instance.sms_notification)
instance.save()
else:
instance = UserNotification.objects.create(user=self.context)
return instance
class UserPhoneOtpSerializer(serializers.ModelSerializer):
"""User Phone serializers"""
class Meta(object):
"""Meta info"""
model = UserPhoneOtp
fields = '__all__'
@@ -1,54 +0,0 @@
<!DOCTYPE HTML>
{% block subject %}DinDin{% endblock %}
{% load static %}
{% block html %}
<html lang="en" style="height: 100%;">
<head>
<meta http-equiv="Content-Type"
content="text/html; charset=UTF-8">
<meta name="viewport"
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
<title>Zod Bank | OTP</title>
<style type="text/css">
@media all and (max-width: 599px) {
.block {
display: block !important;
width: 100%;
}
.top-space {
padding: 15px 0 0 !important;
}
}
</style>
</head>
<body style="margin: 0; padding: 0; background: #f7f8f9; height: 100%;">
<!-- begin template body -->
<table style="background:#f7f8f9; border: 0; border-collapse: separate; border-spacing: 0px; margin: auto; width: 100%; font-family: Arial, Helvetica, sans-serif; height: 100%;" aria-describedby="email-data-wrapper">
<tr>
<td style="padding: 0;">
<table style="background-color: white; border-collapse: separate; border-spacing: 0px; border: 1px solid #e4e8eb; width: 100%; max-width: 600px; margin-right:auto; margin-left: auto;
font-family: Arial, Helvetica, sans-serif;" aria-describedby="email-data-wrapper">
<tr>
<td style="padding: 41px 30px 40px; height: 39px; background: url({% static 'images/backgrounds/email_template.png' %}) left center no-repeat; background-size: cover;">
</td>
</tr>
{% block plain %}
{% endblock %}
<tr>
<td style="padding: 0 27px;">
<p style="margin: 0; font-size: 14px; line-height: 20px; color: #505050; font-weight: 400;">-</p>
<p style="margin: 0; font-size: 14px; line-height: 20px; color: #505050; font-weight: 400;">Cheers!</p>
<p style="margin: 0 0 30px; font-size: 14px; line-height: 20px; color: #505050; font-weight: 700;">Zod Bank Team</p>
</td>
</tr>
</table>
</td>
</tr>
</tbody>
</table>
<!-- end template body -->
</body>
</html>
{% endblock %}
@@ -1,23 +0,0 @@
{% extends "templated_email/email_base.email" %}
{% block subject %}
OTP Verification
{% endblock %}
{% block plain %}
<tr>
<td style="padding: 0 27px 15px;">
<p style="margin: 0; font-size: 16px; line-height: 20px; padding: 36px 0 0; font-weight: 500; color: #1f2532;">
Hi User,
</p>
</td>
</tr>
<tr>
<td style="padding: 0 27px 22px;">
<p style="margin: 0;font-size: 14px; font-weight: 400; line-height: 21px; color: #1f2532;">
You are receiving this email for email verification. Please use <b>{{ otp }} </b>as the verification code for your email address & username.
</p>
</td>
</tr>
{% endblock %}
@@ -1,23 +0,0 @@
{% extends "templated_email/email_base.email" %}
{% block subject %}
Password Reset Verification Code
{% endblock %}
{% block plain %}
<tr>
<td style="padding: 0 27px 15px;">
<p style="margin: 0; font-size: 16px; line-height: 20px; padding: 36px 0 0; font-weight: 500; color: #1f2532;">
Hi User,
</p>
</td>
</tr>
<tr>
<td style="padding: 0 27px 22px;">
<p style="margin: 0;font-size: 14px; font-weight: 400; line-height: 21px; color: #1f2532;">
You are receiving this email for reset password verification. Please use <b>{{ verification_code }} </b>as the verification code.
</p>
</td>
</tr>
{% endblock %}
@@ -1,23 +0,0 @@
{% extends "templated_email/email_base.email" %}
{% block subject %}
Approval Email
{% endblock %}
{% block plain %}
<tr>
<td style="padding: 0 27px 15px;">
<p style="margin: 0; font-size: 16px; line-height: 20px; padding: 36px 0 0; font-weight: 500; color: #1f2532;">
Hi {{full_name}},
</p>
</td>
</tr>
<tr>
<td style="padding: 0 27px 22px;">
<p style="margin: 0;font-size: 14px; font-weight: 400; line-height: 21px; color: #1f2532;">
Please approve {{full_name}} for accessing the Zod bank platform as a junior.
</p>
</td>
</tr>
{% endblock %}
@@ -1,24 +0,0 @@
{% extends "templated_email/email_base.email" %}
{% block subject %}
Invitation Email
{% endblock %}
{% block plain %}
<tr>
<td style="padding: 0 27px 15px;">
<p style="margin: 0; font-size: 16px; line-height: 20px; padding: 36px 0 0; font-weight: 500; color: #1f2532;">
Hi {{full_name}},
</p>
</td>
</tr>
<tr>
<td style="padding: 0 27px 22px;">
<p style="margin: 0;font-size: 14px; font-weight: 400; line-height: 21px; color: #1f2532;">
You are receiving this email for joining the ZOD bank platform. Please use <b>{{ url }} </b> link to join the platform.
<br> Your credentials are:- username = <b>{{email}}</b> and password <b>{{password}}</b><br> <br>Below are the steps to complete the account and how to use this platform.
</p>
</td>
</tr>
{% endblock %}
@@ -1,22 +0,0 @@
{% extends "templated_email/email_base.email" %}
{% block subject %}
{{subject}}
{% endblock %}
{% block plain %}
<tr>
<td style="padding: 0 27px 15px;">
<p style="margin: 0; font-size: 16px; line-height: 20px; padding: 36px 0 0; font-weight: 500; color: #1f2532;">
Hi {{name}},
</p>
</td>
</tr>
<tr>
<td style="padding: 0 27px 22px;">
<p style="margin: 0;font-size: 14px; font-weight: 400; line-height: 21px; color: #1f2532;">
<b>{{name}}</b> have some queries and need some support. Please support them by using their email address <b> {{sender}}</b>. <br> <br> <b>Queries are:- </b> <br> {{ message }}
</p>
</td>
</tr>
{% endblock %}
-5
View File
@@ -1,5 +0,0 @@
"""Test cases file of account"""
"""Django import"""
from django.test import TestCase
# Create your tests here.
-70
View File
@@ -1,70 +0,0 @@
""" Urls files"""
"""Django import"""
from django.urls import path, include
"""Third party import"""
from rest_framework import routers
# Import view functions
# UserLogin views,
# SendPhoneOtp views,
# UserPhoneVerification views,
# UserEmailVerification views,
# ReSendEmailOtp views,
# ForgotPasswordAPIView views,
# ResetPasswordAPIView views,
# ChangePasswordAPIView views,
# UpdateProfileImage views,
# GoogleLoginViewSet views,
# SigninWithApple views,
# ProfileAPIViewSet views,
# UploadImageAPIViewSet views,
# DefaultImageAPIViewSet views,
# DeleteUserProfileAPIViewSet views,
# UserNotificationAPIViewSet views,
# UpdateUserNotificationAPIViewSet views,
# SendSupportEmail views,
# LogoutAPIView views,
# AccessTokenAPIView views"""
from .views import (UserLogin, SendPhoneOtp, UserPhoneVerification, UserEmailVerification, ReSendEmailOtp,
ForgotPasswordAPIView, ResetPasswordAPIView, ChangePasswordAPIView, UpdateProfileImage,
GoogleLoginViewSet, SigninWithApple, ProfileAPIViewSet, UploadImageAPIViewSet,
DefaultImageAPIViewSet, DeleteUserProfileAPIViewSet, UserNotificationAPIViewSet,
UpdateUserNotificationAPIViewSet, SendSupportEmail, LogoutAPIView, AccessTokenAPIView)
"""Router"""
router = routers.SimpleRouter()
"""API End points with router"""
router.register('user', UserLogin, basename='user')
"""super admin login"""
router.register('admin', UserLogin, basename='admin')
"""google login end point"""
router.register('google-login', GoogleLoginViewSet, basename='admin')
router.register('send-phone-otp', SendPhoneOtp, basename='send-phone-otp')
router.register('user-phone-verification', UserPhoneVerification, basename='user-phone-verification')
"""email verification end point"""
router.register('user-email-verification', UserEmailVerification, basename='user-email-verification')
"""Resend email otp end point"""
router.register('resend-email-otp', ReSendEmailOtp, basename='resend-email-otp')
"""Profile end point"""
router.register('profile', ProfileAPIViewSet, basename='profile')
"""Upload default task image end point"""
router.register('upload-default-task-image', UploadImageAPIViewSet, basename='upload-default-task-image')
"""Fetch default task image end point"""
router.register('default-task-image', DefaultImageAPIViewSet, basename='default-task-image')
"""Delete user account"""
router.register('delete', DeleteUserProfileAPIViewSet, basename='delete')
"""user account notification"""
router.register('user-notification', UserNotificationAPIViewSet, basename='user-notification')
"""update user account notification"""
router.register('update-user-notification', UpdateUserNotificationAPIViewSet, basename='update-user-notification')
"""Define url pattern"""
urlpatterns = [
path('api/v1/', include(router.urls)),
path('api/v1/forgot-password/', ForgotPasswordAPIView.as_view()),
path('api/v1/reset-password/', ResetPasswordAPIView.as_view()),
path('api/v1/change-password/', ChangePasswordAPIView.as_view()),
path('api/v1/update-profile-image/', UpdateProfileImage.as_view()),
path('api/v1/apple-login/', SigninWithApple.as_view(), name='signup_with_apple'),
path('api/v1/send-support-email/', SendSupportEmail.as_view(), name='send-support-email'),
path('api/v1/logout/', LogoutAPIView.as_view(), name='logout'),
path('api/v1/generate-token/', AccessTokenAPIView.as_view(), name='generate-token')
]
-230
View File
@@ -1,230 +0,0 @@
"""Account utils"""
"""Import django"""
from django.conf import settings
from rest_framework import viewsets, status
from rest_framework.response import Response
"""Third party Django app"""
from templated_email import send_templated_mail
import jwt
import string
from datetime import datetime
from calendar import timegm
from uuid import uuid4
# Import secrets module for generating random number
import secrets
from rest_framework import serializers
# Django App Import
# Import models from junior App,
# Import models from guardian App,
# Import models from account App,
# Import messages from base package"""
from junior.models import Junior
from guardian.models import Guardian
from account.models import UserDelete
from base.messages import ERROR_CODE
# Define delete
# user account condition,
# Define delete
# user account
# condition for social
# login account,
# Update junior account,
# Update guardian account,
# Define custom email for otp verification,
# Define support email for user's query,
# Define custom success response,
# Define custom error response,
# Generate access token,
# refresh token by using jwt,
# Define function for generating
# guardian code, junior code,
# referral code,
# Define function for generating
# alphanumeric code
def delete_user_account_condition(user, user_type_data, user_type, user_tb, data, random_num):
"""delete user account"""
if user_type == '1' and user_type_data == '1':
junior_account_update(user_tb)
elif user_type == '2' and user_type_data == '2':
guardian_account_update(user_tb)
else:
raise serializers.ValidationError({"details": ERROR_CODE['2030'], "code": "400", "status": "failed"})
user_tb.email = str(random_num) + str('@D_') + '{}'.format(user_tb.username).lower()
user_tb.username = str(random_num) + str('@D_') + '{}'.format(user_tb.username).lower()
d_email = user_tb.email
o_mail = user.email
# update user email with dummy email
user_tb.save()
"""create object in user delete model"""
instance = UserDelete.objects.create(user=user_tb, d_email=d_email, old_email=o_mail,
is_active=True, reason=data)
return instance
def delete_user_account_condition_social(user, user_type,user_tb, data, random_num):
"""delete user account"""
if user_type == '1':
junior_account_update(user_tb)
elif user_type == '2':
guardian_account_update(user_tb)
else:
raise serializers.ValidationError({"details": ERROR_CODE['2030'], "code": "400", "status": "failed"})
user_tb.email = str(random_num) + str('@D_') + '{}'.format(user_tb.username).lower()
user_tb.username = str(random_num) + str('@D_') + '{}'.format(user_tb.username).lower()
dummy_email = user_tb.email
old_mail = user.email
# update user email with dummy email
user_tb.save()
"""create object in user delete model"""
instance_data = UserDelete.objects.create(user=user_tb, d_email=dummy_email, old_email=old_mail,
is_active=True, reason=data)
return instance_data
def junior_account_update(user_tb):
"""junior account delete"""
junior_data = Junior.objects.filter(auth__email=user_tb.email).first()
if junior_data:
# Update junior account
junior_data.is_active = False
junior_data.is_verified = False
junior_data.guardian_code = '{}'
junior_data.save()
def guardian_account_update(user_tb):
"""update guardian account after delete the user account"""
guardian_data = Guardian.objects.filter(user__email=user_tb.email).first()
if guardian_data:
# Update guardian account
guardian_data.is_active = False
guardian_data.is_verified = False
guardian_data.save()
jun_data = Junior.objects.filter(guardian_code__icontains=str(guardian_data.guardian_code))
"""Disassociate relation between guardian and junior"""
for data in jun_data:
data.guardian_code.remove(guardian_data.guardian_code)
data.save()
def send_otp_email(recipient_email, otp):
"""Send otp on email with template"""
from_email = settings.EMAIL_FROM_ADDRESS
recipient_list = [recipient_email]
"""Send otp on email"""
send_templated_mail(
template_name='email_otp_verification.email',
from_email=from_email,
recipient_list=recipient_list,
context={
'otp': otp
}
)
return otp
def send_support_email(name, sender, subject, message):
"""Send otp on email with template"""
to_email = [settings.EMAIL_FROM_ADDRESS]
from_email = settings.DEFAULT_ADDRESS
"""Send support email to zod bank support team"""
send_templated_mail(
template_name='support_mail.email',
from_email=from_email,
recipient_list=to_email,
context={
'name': name.title(),
'sender': sender,
'subject': subject,
'message': message
}
)
return name
def custom_response(detail, data=None, response_status=status.HTTP_200_OK):
"""Custom response code"""
if not data:
"""when data is none"""
data = None
return Response({"data": data, "message": detail, "status": "success", "code": response_status})
def custom_error_response(detail, response_status):
"""
function is used for getting same global error response for all
:param detail: error message .
:param response_status: http status.
:return: Json response
"""
if not detail:
"""when details is empty"""
detail = {}
return Response({"error": detail, "status": "failed", "code": response_status})
def get_user_data(attrs):
"""
used to decode token
"""
user_data = jwt.decode(jwt=attrs['token'], options={'verify_signature': False},
algorithms=['RS256'])
return user_data
def generate_jwt_token(token_type: str, now_time: int, data: dict = dict):
"""
used to generate jwt token
"""
if type(data) == type:
data = {}
"""Update data dictionary"""
data.update({
'token_type': token_type,
'iss': 'your_site_url',
'iat': timegm(datetime.utcnow().utctimetuple()),
'jti': uuid4().hex
})
"""Access and Refresh token"""
TOKEN_TYPE = ["access", "refresh"]
if token_type == TOKEN_TYPE[1]:
"""Refresh token"""
exp = now_time + settings.SIMPLE_JWT['REFRESH_TOKEN_LIFETIME']
else:
"""access token"""
exp = now_time + settings.SIMPLE_JWT['ACCESS_TOKEN_LIFETIME']
data.update({
"exp": timegm(exp.utctimetuple())
})
signing_key = secrets.token_hex(32)
return jwt.encode(payload=data, key=signing_key,
algorithm='HS256')
def get_token(data: dict = dict):
""" create access and refresh token """
now_time = datetime.utcnow()
"""generate access token"""
access = generate_jwt_token('access', now_time, data)
"""generate refresh token"""
refresh = generate_jwt_token('refresh', now_time, data)
return {
'access': access,
'refresh': refresh
}
def generate_alphanumeric_code(length):
"""Generate alphanumeric code"""
alphabet = string.ascii_letters + string.digits
code = ''.join(secrets.choice(alphabet) for _ in range(length))
return code
def generate_code(value, user_id):
"""generate referral, junior and guardian code"""
code = value + str(user_id).zfill(3)
return code
-557
View File
@@ -1,557 +0,0 @@
"""Account view """
from notifications.utils import remove_fcm_token
"""Django import"""
from datetime import datetime, timedelta
from rest_framework import viewsets, status, views
from rest_framework.decorators import action
import random
import logging
from django.utils import timezone
import jwt
from django.contrib.auth import logout
from django.contrib.auth import authenticate, login
from rest_framework.permissions import IsAuthenticated
from templated_email import send_templated_mail
import google.oauth2.credentials
import google.auth.transport.requests
from rest_framework import status
import requests
from rest_framework.response import Response
from django.conf import settings
"""App Import"""
from guardian.models import Guardian
from junior.models import Junior
from guardian.utils import upload_image_to_alibaba
from account.models import UserDeviceDetails, UserPhoneOtp, UserEmailOtp, DefaultTaskImages, UserNotification
from django.contrib.auth.models import User
"""Account serializer"""
from .serializers import (SuperUserSerializer, GuardianSerializer, JuniorSerializer, EmailVerificationSerializer,
ForgotPasswordSerializer, ResetPasswordSerializer, ChangePasswordSerializer,
GoogleLoginSerializer, UpdateGuardianImageSerializer, UpdateJuniorProfileImageSerializer,
DefaultTaskImagesSerializer, DefaultTaskImagesDetailsSerializer, UserDeleteSerializer,
UserNotificationSerializer, UpdateUserNotificationSerializer, UserPhoneOtpSerializer)
from rest_framework_simplejwt.tokens import RefreshToken
from base.messages import ERROR_CODE, SUCCESS_CODE
from base.constants import NUMBER, ZOD, JUN, GRD
from guardian.tasks import generate_otp
from account.utils import (send_otp_email, send_support_email, custom_response, custom_error_response,
generate_code)
from junior.serializers import JuniorProfileSerializer
from guardian.serializers import GuardianProfileSerializer
class GoogleLoginMixin(object):
"""google login mixin"""
@staticmethod
def google_login(self, request):
"""google login function"""
access_token = request.data.get('access_token')
user_type = request.data.get('user_type')
if not access_token:
return Response({'error': 'Access token is required.'}, status=status.HTTP_400_BAD_REQUEST)
try:
# Validate the access token and obtain the user's email and name
credentials = google.oauth2.credentials.Credentials.from_authorized_user_info(
info={
'access_token': access_token,
'token_uri': 'https://oauth2.googleapis.com/token',
'client_id': settings.GOOGLE_CLIENT_ID,
'client_secret': settings.GOOGLE_CLIENT_SECRET,
'refresh_token': None,
}
)
user_info_endpoint = f'https://www.googleapis.com/oauth2/v3/userinfo?access_token={access_token}'
headers = {'Authorization': f'Bearer {credentials.token}'}
response = requests.get(user_info_endpoint, headers=headers)
response.raise_for_status()
user_info = response.json()
email = user_info['email']
first_name = user_info['given_name']
last_name = user_info['family_name']
profile_picture = user_info['picture']
except Exception as e:
return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST)
# Check if the user exists in your database or create a new user
# ...
user_data = User.objects.filter(email__iexact=email)
if user_data.exists():
if str(user_type) == '1':
junior_query = Junior.objects.filter(auth=user_data.last()).last()
serializer = JuniorSerializer(junior_query)
if str(user_type) == '2':
guardian_query = Guardian.objects.filter(user=user_data.last()).last()
serializer = GuardianSerializer(guardian_query)
return custom_response(SUCCESS_CODE['3003'], serializer.data,
response_status=status.HTTP_200_OK)
if not User.objects.filter(email__iexact=email).exists():
user_obj = User.objects.create(username=email, email=email, first_name=first_name, last_name=last_name)
if str(user_type) == '1':
junior_query = Junior.objects.create(auth=user_obj, is_verified=True, is_active=True,
image=profile_picture, signup_method='2',
junior_code=generate_code(JUN, user_obj.id),
referral_code=generate_code(ZOD, user_obj.id)
)
serializer = JuniorSerializer(junior_query)
if str(user_type) == '2':
guardian_query = Guardian.objects.create(user=user_obj, is_verified=True, is_active=True,
image=profile_picture,signup_method='2',
guardian_code=generate_code(GRD, user_obj.id),
referral_code=generate_code(ZOD, user_obj.id)
)
serializer = GuardianSerializer(guardian_query)
# Return a JSON response with the user's email and name
return custom_response(SUCCESS_CODE['3003'], serializer.data,
response_status=status.HTTP_200_OK)
class GoogleLoginViewSet(GoogleLoginMixin, viewsets.GenericViewSet):
"""Google login viewset"""
serializer_class = GoogleLoginSerializer
def create(self, request):
"""create method"""
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
return self.google_login(request)
class SigninWithApple(views.APIView):
"""This API is for sign in with Apple for app."""
def post(self, request):
token = request.data.get("access_token")
user_type = request.data.get("user_type")
try:
decoded_data = jwt.decode(token, options={"verify_signature": False})
user_data = {"email": decoded_data.get('email'), "username": decoded_data.get('email'), "is_active": True}
if decoded_data.get("email"):
try:
user = User.objects.get(email=decoded_data.get("email"))
if str(user_type) == '1':
junior_query = Junior.objects.filter(auth=user).last()
serializer = JuniorSerializer(junior_query)
if str(user_type) == '2':
guardian_query = Guardian.objects.filter(user=user).last()
serializer = GuardianSerializer(guardian_query)
return custom_response(SUCCESS_CODE['3003'], serializer.data,
response_status=status.HTTP_200_OK)
except User.DoesNotExist:
user = User.objects.create(**user_data)
if str(user_type) == '1':
junior_query = Junior.objects.create(auth=user, is_verified=True, is_active=True,
signup_method='3',
junior_code=generate_code(JUN, user.id),
referral_code=generate_code(ZOD, user.id))
serializer = JuniorSerializer(junior_query)
if str(user_type) == '2':
guardian_query = Guardian.objects.create(user=user, is_verified=True, is_active=True,
signup_method='3',
guardian_code=generate_code(GRD, user.id),
referral_code=generate_code(ZOD, user.id))
serializer = GuardianSerializer(guardian_query)
return custom_response(SUCCESS_CODE['3003'], serializer.data,
response_status=status.HTTP_200_OK)
except Exception as e:
logging.error(e)
return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST)
class UpdateProfileImage(views.APIView):
"""Update profile image"""
permission_classes = [IsAuthenticated]
def put(self, request, format=None):
try:
image = request.data['image']
if image and image.size == NUMBER['zero']:
return custom_error_response(ERROR_CODE['2035'], response_status=status.HTTP_400_BAD_REQUEST)
filename = f"images/{image.name}"
image_url = upload_image_to_alibaba(image, filename)
image_data = image_url
if str(request.data['user_type']) == '1':
junior_query = Junior.objects.filter(auth=request.user).last()
serializer = UpdateJuniorProfileImageSerializer(junior_query,
data={'image':image_data}, partial=True)
elif str(request.data['user_type']) == '2':
guardian_query = Guardian.objects.filter(user=request.user).last()
serializer = UpdateGuardianImageSerializer(guardian_query,
data={'image':image_data}, partial=True)
if serializer.is_valid():
serializer.save()
return custom_response(SUCCESS_CODE['3017'], serializer.data, response_status=status.HTTP_200_OK)
return custom_error_response(serializer.errors, response_status=status.HTTP_400_BAD_REQUEST)
except Exception as e:
logging.error(e)
return custom_error_response(ERROR_CODE['2036'],response_status=status.HTTP_400_BAD_REQUEST)
class ChangePasswordAPIView(views.APIView):
"""change password"""
serializer_class = ChangePasswordSerializer
permission_classes = [IsAuthenticated]
def post(self, request):
serializer = ChangePasswordSerializer(context=request.user, data=request.data)
if serializer.is_valid():
serializer.save()
return custom_response(SUCCESS_CODE['3007'], response_status=status.HTTP_200_OK)
return custom_error_response(serializer.errors, response_status=status.HTTP_400_BAD_REQUEST)
class ResetPasswordAPIView(views.APIView):
"""Reset password"""
def post(self, request):
serializer = ResetPasswordSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return custom_response(SUCCESS_CODE['3006'], response_status=status.HTTP_200_OK)
return custom_error_response(serializer.errors, response_status=status.HTTP_400_BAD_REQUEST)
class ForgotPasswordAPIView(views.APIView):
"""Forgot password"""
def post(self, request):
serializer = ForgotPasswordSerializer(data=request.data)
if serializer.is_valid():
email = serializer.validated_data['email']
try:
User.objects.get(email=email)
except User.DoesNotExist:
return custom_error_response(ERROR_CODE['2004'], response_status=status.HTTP_404_NOT_FOUND)
verification_code = generate_otp()
# Send the verification code to the user's email
from_email = settings.EMAIL_FROM_ADDRESS
recipient_list = [email]
send_templated_mail(
template_name='email_reset_verification.email',
from_email=from_email,
recipient_list=recipient_list,
context={
'verification_code': verification_code
}
)
expiry = timezone.now() + timezone.timedelta(days=1)
user_data, created = UserEmailOtp.objects.get_or_create(email=email)
if created:
user_data.expired_at = expiry
user_data.save()
if user_data:
user_data.otp = verification_code
user_data.expired_at = expiry
user_data.save()
return custom_response(SUCCESS_CODE['3015'],
response_status=status.HTTP_200_OK)
return custom_error_response(serializer.errors, response_status=status.HTTP_400_BAD_REQUEST)
class SendPhoneOtp(viewsets.ModelViewSet):
"""Send otp on phone"""
queryset = UserPhoneOtp.objects.all()
serializer_class = UserPhoneOtpSerializer
def create(self, request, *args, **kwargs):
otp = generate_otp()
phone_number = self.request.data['phone']
if phone_number.isdigit() and len(phone_number) == 10:
phone_otp, created = UserPhoneOtp.objects.get_or_create(country_code=self.request.data['country_code'],
phone=self.request.data['phone'])
if phone_otp:
phone_otp.otp = otp
phone_otp.save()
return custom_response(None, {'phone_otp':otp}, response_status=status.HTTP_200_OK)
return custom_error_response(ERROR_CODE['2020'], response_status=status.HTTP_400_BAD_REQUEST)
class UserPhoneVerification(viewsets.ModelViewSet):
"""Send otp on phone"""
queryset = UserPhoneOtp.objects.all()
serializer_class = UserPhoneOtpSerializer
def list(self, request, *args, **kwargs):
try:
phone_data = UserPhoneOtp.objects.filter(phone=self.request.GET.get('phone'),
otp=self.request.GET.get('otp')).last()
if phone_data:
phone_data.is_verified = True
phone_data.save()
return custom_response(SUCCESS_CODE['3012'], response_status=status.HTTP_200_OK)
else:
return custom_error_response(ERROR_CODE["2008"], response_status=status.HTTP_400_BAD_REQUEST)
except Exception:
return custom_error_response(ERROR_CODE["2008"], response_status=status.HTTP_400_BAD_REQUEST)
class UserLogin(viewsets.ViewSet):
"""User login"""
@action(methods=['post'], detail=False)
def login(self, request):
username = request.data.get('username')
password = request.data.get('password')
device_id = request.META.get('HTTP_DEVICE_ID')
user = authenticate(request, username=username, password=password)
try:
if user is not None:
login(request, user)
guardian_data = Guardian.objects.filter(user__username=username, is_verified=True).last()
if guardian_data:
serializer = GuardianSerializer(guardian_data).data
junior_data = Junior.objects.filter(auth__username=username, is_verified=True).last()
if junior_data:
serializer = JuniorSerializer(junior_data).data
device_details, created = UserDeviceDetails.objects.get_or_create(user=user)
if device_details:
device_details.device_id = device_id
device_details.save()
return custom_response(SUCCESS_CODE['3003'], serializer, response_status=status.HTTP_200_OK)
else:
return custom_error_response(ERROR_CODE["2002"], response_status=status.HTTP_401_UNAUTHORIZED)
except Exception as e:
logging.error(e)
email_verified = UserEmailOtp.objects.filter(email=username).last()
refresh = RefreshToken.for_user(user)
access_token = str(refresh.access_token)
refresh_token = str(refresh)
data = {"auth_token":access_token, "refresh_token":refresh_token, "is_profile_complete": False,
"user_type": email_verified.user_type,
}
is_verified = False
if email_verified:
is_verified = email_verified.is_verified
if not is_verified:
otp = generate_otp()
email_verified.otp = otp
email_verified.save()
data.update({"email_otp":otp})
send_otp_email(username, otp)
return custom_response(ERROR_CODE['2024'], {"email_otp": otp, "is_email_verified": is_verified},
response_status=status.HTTP_200_OK)
data.update({"is_email_verified": is_verified})
return custom_response(SUCCESS_CODE['3003'], data, response_status=status.HTTP_200_OK)
@action(methods=['post'], detail=False)
def admin_login(self, request):
username = request.data.get('username')
password = request.data.get('password')
user = authenticate(request, username=username, password=password)
try:
if user is not None:
login(request, user)
if user.is_superuser:
serializer = SuperUserSerializer(user)
return custom_response(SUCCESS_CODE['3003'], serializer.data, response_status=status.HTTP_200_OK)
else:
return custom_error_response(ERROR_CODE["2002"], response_status=status.HTTP_401_UNAUTHORIZED)
except Exception as e:
logging.error(e)
refresh = RefreshToken.for_user(user)
access_token = str(refresh.access_token)
refresh_token = str(refresh)
data = {"auth_token": access_token, "refresh_token":refresh_token, "user_type": '3'}
return custom_response(None, data, response_status=status.HTTP_200_OK)
class UserEmailVerification(viewsets.ModelViewSet):
"""User Email verification"""
serializer_class = EmailVerificationSerializer
queryset = UserEmailOtp.objects.all()
def list(self, request, *args, **kwargs):
try:
user_obj = User.objects.filter(username=self.request.GET.get('email')).last()
email_data = UserEmailOtp.objects.filter(email=self.request.GET.get('email'),
otp=self.request.GET.get('otp')).last()
if email_data:
input_datetime_str = str(email_data.expired_at)
input_format = "%Y-%m-%d %H:%M:%S.%f%z"
output_format = "%Y-%m-%d %H:%M:%S.%f"
input_datetime = datetime.strptime(input_datetime_str, input_format)
output_datetime_str = input_datetime.strftime(output_format)
format_str = "%Y-%m-%d %H:%M:%S.%f"
datetime_obj = datetime.strptime(output_datetime_str, format_str)
if datetime.today() > datetime_obj:
return custom_error_response(ERROR_CODE["2029"], response_status=status.HTTP_400_BAD_REQUEST)
email_data.is_verified = True
email_data.save()
if email_data.user_type == '1':
junior_data = Junior.objects.filter(auth__email=self.request.GET.get('email')).last()
if junior_data:
junior_data.is_verified = True
junior_data.save()
else:
guardian_data = Guardian.objects.filter(user__email=self.request.GET.get('email')).last()
if guardian_data:
guardian_data.is_verified = True
guardian_data.save()
refresh = RefreshToken.for_user(user_obj)
access_token = str(refresh.access_token)
refresh_token = str(refresh)
return custom_response(SUCCESS_CODE['3011'], {"auth_token":access_token,
"refresh_token":refresh_token},
response_status=status.HTTP_200_OK)
else:
return custom_error_response(ERROR_CODE["2008"], response_status=status.HTTP_400_BAD_REQUEST)
except Exception as e:
logging.error(e)
return custom_error_response(ERROR_CODE["2008"], response_status=status.HTTP_400_BAD_REQUEST)
class ReSendEmailOtp(viewsets.ModelViewSet):
"""Send otp on phone"""
queryset = UserEmailOtp.objects.all()
serializer_class = EmailVerificationSerializer
permission_classes = [IsAuthenticated]
def create(self, request, *args, **kwargs):
otp = generate_otp()
if User.objects.filter(email=request.data['email']):
expiry = timezone.now() + timezone.timedelta(days=1)
email_data, created = UserEmailOtp.objects.get_or_create(email=request.data['email'])
if created:
email_data.expired_at = expiry
email_data.save()
if email_data:
email_data.otp = otp
email_data.expired_at = expiry
email_data.save()
send_otp_email(request.data['email'], otp)
return custom_response(SUCCESS_CODE['3016'], response_status=status.HTTP_200_OK)
else:
return custom_error_response(ERROR_CODE["2023"], response_status=status.HTTP_400_BAD_REQUEST)
class ProfileAPIViewSet(viewsets.ModelViewSet):
"""Profile viewset"""
queryset = User.objects.all()
serializer_class = JuniorProfileSerializer
permission_classes = [IsAuthenticated]
def list(self, request, *args, **kwargs):
"""profile view"""
if str(self.request.GET.get('user_type')) == '1':
junior_data = Junior.objects.filter(auth=self.request.user).last()
if junior_data:
serializer = JuniorProfileSerializer(junior_data)
return custom_response(None, serializer.data, response_status=status.HTTP_200_OK)
elif str(self.request.GET.get('user_type')) == '2':
guardian_data = Guardian.objects.filter(user=self.request.user).last()
if guardian_data:
serializer = GuardianProfileSerializer(guardian_data)
return custom_response(None, serializer.data, response_status=status.HTTP_200_OK)
class UploadImageAPIViewSet(viewsets.ModelViewSet):
"""upload task image"""
queryset = DefaultTaskImages.objects.all()
serializer_class = DefaultTaskImagesSerializer
def create(self, request, *args, **kwargs):
"""upload images"""
image_data = request.data['image_url']
filename = f"default_task_images/{image_data.name}"
if image_data.size == NUMBER['zero']:
return custom_error_response(ERROR_CODE['2035'], response_status=status.HTTP_400_BAD_REQUEST)
image = upload_image_to_alibaba(image_data, filename)
image_data = image
request.data['image_url'] = image_data
serializer = DefaultTaskImagesSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return custom_response(None, serializer.data, response_status=status.HTTP_200_OK)
return custom_error_response(serializer.error, response_status=status.HTTP_400_BAD_REQUEST)
class DefaultImageAPIViewSet(viewsets.ModelViewSet):
"""Profile viewset"""
queryset = DefaultTaskImages.objects.all()
serializer_class = DefaultTaskImagesDetailsSerializer
permission_classes = [IsAuthenticated]
def list(self, request, *args, **kwargs):
"""profile view"""
queryset = DefaultTaskImages.objects.all()
serializer = DefaultTaskImagesSerializer(queryset, many=True)
return custom_response(None, serializer.data, response_status=status.HTTP_200_OK)
class DeleteUserProfileAPIViewSet(viewsets.GenericViewSet):
""" Delete user API view set """
@action(detail=False, methods=['POST'], url_path='user-account',serializer_class=UserDeleteSerializer,
permission_classes=[IsAuthenticated])
def account(self, request):
user_type = str(request.data['user_type'])
password = request.data.get('password')
signup_method = str(request.data.get('signup_method'))
serializer = self.get_serializer(data=request.data, context={'request': request, 'user': request.user,
'user_type': user_type,
'password': password,
'signup_method':signup_method})
if serializer.is_valid():
serializer.save()
return custom_response(SUCCESS_CODE['3005'], response_status=status.HTTP_200_OK)
return custom_error_response(serializer.errors, response_status=status.HTTP_400_BAD_REQUEST)
class UserNotificationAPIViewSet(viewsets.ModelViewSet):
"""notification viewset"""
queryset = UserNotification.objects.all()
serializer_class = UserNotificationSerializer
permission_classes = [IsAuthenticated]
def list(self, request, *args, **kwargs):
"""profile view"""
queryset = self.queryset.filter(user=request.user)
serializer = UserNotificationSerializer(queryset, many=True)
return custom_response(None, serializer.data, response_status=status.HTTP_200_OK)
class UpdateUserNotificationAPIViewSet(viewsets.ModelViewSet):
"""Update notification viewset"""
queryset = UserNotification.objects.all()
serializer_class = UpdateUserNotificationSerializer
permission_classes = [IsAuthenticated]
def create(self, request, *args, **kwargs):
"""profile view"""
serializer = UpdateUserNotificationSerializer(data=request.data,
context=request.user)
if serializer.is_valid():
serializer.save()
return custom_response(None, serializer.data, response_status=status.HTTP_200_OK)
return custom_error_response(serializer.error, response_status=status.HTTP_400_BAD_REQUEST)
class SendSupportEmail(views.APIView):
"""support email api"""
permission_classes = (IsAuthenticated,)
def post(self, request):
name = request.data.get('name')
sender = request.data.get('email')
subject = request.data.get('subject')
message = request.data.get('message')
if name and sender and subject and message:
try:
send_support_email(name, sender, subject, message)
return custom_response(SUCCESS_CODE['3019'], response_status=status.HTTP_200_OK)
except Exception as e:
return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST)
else:
return custom_error_response(ERROR_CODE['2033'], response_status=status.HTTP_400_BAD_REQUEST)
class LogoutAPIView(views.APIView):
"""Log out API"""
permission_classes = (IsAuthenticated,)
def post(self, request):
remove_fcm_token(
request.auth.payload['user_id'],
request.META['HTTP_AUTHORIZATION'].split(" ")[1],
request.data.get('registration_id', ""))
logout(request)
request.session.flush()
return custom_response(SUCCESS_CODE['3020'], response_status=status.HTTP_200_OK)
class AccessTokenAPIView(views.APIView):
"""generate access token API"""
def post(self, request):
# Assuming you have a refresh_token string
refresh_token = request.data['refresh_token']
# Create a RefreshToken instance from the refresh token string
refresh = RefreshToken(refresh_token)
# Generate a new access token
access_token = str(refresh.access_token)
data = {"auth_token": access_token}
return custom_response(None, data, response_status=status.HTTP_200_OK)
-3
View File
@@ -1,3 +0,0 @@
"""
This is init module of the Project Zod Bank
"""
-30
View File
@@ -1,30 +0,0 @@
"""
Common send_mail function
"""
import logging
from django.core.mail import EmailMultiAlternatives
def send_mail(subject, message, from_email, recipient_list, html_message=None, cc=None,
fail_silently=False):
"""
Send Email
:param subject:
:param message:
:param from_email:
:param recipient_list:
:param html_message:
:param cc:
:param fail_silently:
:return:
"""
try:
mail = EmailMultiAlternatives(subject, message, from_email, recipient_list, cc)
if html_message:
mail.attach_alternative(html_message, 'text/html')
return mail.send(fail_silently)
except Exception as e:
logging.error(e)
return False
-105
View File
@@ -1,105 +0,0 @@
"""
This module contains constants used throughout the project
"""
import os
# GOOGLE_URL used for interact with google server to verify user existence.
#GOOGLE_URL = "https://www.googleapis.com/plus/v1/"
# Define Code prefix word
# for guardian code,
# junior code,
# referral code"""
ZOD = 'ZOD'
JUN = 'JUN'
GRD = 'GRD'
# Define number variable
# from zero to
# twenty and
# some standard
# number"""
NUMBER = {
'point_zero': 0.0, 'zero': 0, 'one': 1, 'two': 2, 'three': 3, 'four': 4, 'five': 5, 'six': 6, 'seven': 7,
'eight': 8, 'nine': 9, 'ten': 10, 'eleven': 11, 'twelve': 12, 'thirteen': 13, 'fourteen': 14, 'fifteen': 15,
'sixteen': 16, 'seventeen': 17, 'eighteen': 18, 'nineteen': 19, 'twenty': 20,
'twenty_one': 21, 'twenty_two': 22,'twenty_three': 23, 'twenty_four': 24, 'twenty_five': 25,
'thirty': 30, 'forty': 40, 'fifty': 50, 'sixty': 60, 'seventy': 70, 'eighty': 80, 'ninty': 90,
'ninety_nine': 99, 'hundred': 100, 'thirty_six_hundred': 3600
}
# Super Admin string constant for 'role'
SUPER_ADMIN = "Super Admin"
# Define jwt_token_expiration time in minutes for now token will expire after 3 days
JWT_TOKEN_EXPIRATION = 3 * 24 * 60
# Define common file extention
FILE_EXTENSION = ("gif", "jpeg", "jpg", "png", "svg")
# Define file size in bytes(5MB = 5 * 1024 * 1024)
FILE_SIZE = 5 * 1024 * 1024
# String constant for configurable date for allocation lock period
ALLOCATION_LOCK_DATE = 1
"""user type"""
USER_TYPE = (
('1', 'junior'),
('2', 'guardian'),
('3', 'superuser')
)
"""gender"""
GENDERS = (
('1', 'Male'),
('2', 'Female')
)
# Task status"""
TASK_STATUS = (
('1', 'pending'),
('2', 'in-progress'),
('3', 'rejected'),
('4', 'requested'),
('5', 'completed')
)
# sign up method
SIGNUP_METHODS = (
('1', 'manual'),
('2', 'google'),
('3', 'apple')
)
# relationship
RELATIONSHIP = (
('1', 'parent'),
('2', 'legal_guardian')
)
"""
Define task status
in a number"""
PENDING = 1
IN_PROGRESS = 2
REJECTED = 3
REQUESTED = 4
COMPLETED = 5
TASK_POINTS = 5
# duplicate name used defined in constant PROJECT_NAME
PROJECT_NAME = 'Zod Bank'
# define user type constant
GUARDIAN = 'guardian'
JUNIOR = 'junior'
SUPERUSER = 'superuser'
# numbers used as a constant
# Define the byte into kb
BYTE_IMAGE_SIZE = 1024
# validate file size
MAX_FILE_SIZE = 1024 * 1024 * 5
ARTICLE_SURVEY_POINTS = 5
MAX_ARTICLE_CARD = 6
# min and max survey
MIN_ARTICLE_SURVEY = 5
MAX_ARTICLE_SURVEY = 10
-16
View File
@@ -1,16 +0,0 @@
"""
This module contains constants used throughout the project
"""
from zod_bank.settings import BUCKET_NAME
# Define S3 folder url
S3_FOLDER_DIR = {
'user_image': 'user_image/',
}
# S3 bucket url
S3_URL = "https://"+BUCKET_NAME+".s3.amazonaws.com/"
S3_FOLDER_URL = {
'user_image_file': S3_URL+S3_FOLDER_DIR['user_image'],
}
-139
View File
@@ -1,139 +0,0 @@
"""
This module contains all the messages used all across the project
"""
ERROR_CODE_REQUIRED = {
# Error code for email address
"1000": ["Required email address not found."],
# Error code for password
"1001": ["Required password not found."],
# Error code for Required Post parameters
"1002": ["Required POST parameters not found."],
# Error code for Required Get parameters
"1003": ["Required GET parameters not found."],
# Error code for Required Headers
"1004": ["Required headers were not found."],
# Error code for Required Put parameters
"1005": ["Required PUT parameters not found."],
# Error code for Required query parameters
"1006": ["Required query parameters is not valid."],
# Error code for Required Head parameters
"1008": ["Required HEAD parameters not found."]
}
# Error code
ERROR_CODE = {
"2000": "Email not found.",
"2001": "This is your existing password. Please choose other one",
"2002": "Invalid login credentials.",
"2003": "An account already exists with this email address.",
"2004": "User not found.",
"2005": "Your account has been activated.",
"2006": "Your account is not activated.",
"2007": "Your account already activated.",
"2008": "Invalid OTP.",
"2009": "The user provided cannot be found or the reset password token has become invalid/timed out.",
"2010": "Invalid Link.",
"2011": "Your profile has not been completed yet.",
"2012": "Phone number already used",
"2013": "Invalid token.",
"2014": "Your old password doesn't match.",
"2015": "Invalid old password.",
"2016": "Invalid search.",
"2017": "{model} object with {pk} does not exist",
"2018": "Attached File not found",
"2019": "Either File extension or File size doesn't meet the requirements",
"2020": "Enter valid mobile number",
"2021": "Already register",
"2022": "Invalid Guardian code",
"2023": "Invalid user",
# email not verified
"2024": "Email not verified",
"2025": "Invalid input. Expected a list of strings.",
# check old and new password
"2026": "New password should not same as old password",
"2027": "data should contain `identityToken`",
"2028": "You are not authorized person to sign up on this platform",
"2029": "Validity of otp verification is expired",
"2030": "Use correct user type and token",
# invalid password
"2031": "Invalid password",
"2032": "Failed to send email",
"2033": "Missing required fields",
"2034": "Junior is not associated",
# image size
"2035": "Image should not be 0 kb",
"2036": "Choose valid user",
# log in multiple device msg
"2037": "You are already log in another device",
"2038": "Choose valid action for task",
# card length limit
"2039": "Add at least one article card or maximum 6",
"2040": "Add at least 5 article survey or maximum 10",
# add article msg
"2041": "Article with given id doesn't exist.",
"2042": "Article Card with given id doesn't exist.",
"2043": "Article Survey with given id doesn't exist.",
"2044": "Task does not exist"
}
"""Success message code"""
SUCCESS_CODE = {
"3000": "ok",
# Success code for password
"3001": "Sign up successfully",
# Success code for Thank you
"3002": "Thank you for contacting us! Our Consumer Experience Team will reach out to you shortly.",
# Success code for account activation
"3003": "Log in successful",
# Success code for password reset
"3004": "Password reset link has been sent to your email address",
# Success code for link verified
"3005": "Your account is deleted successfully.",
# Success code for password reset
"3006": "Your password has been reset successfully.",
# Success code for password update
"3007": "Your password has been changed successfully.",
# Success code for valid link
"3008": "You have a valid link.",
# Success code for logged out
"3009": "You have successfully logged out!",
# Success code for check all fields
"3010": "All fields are valid",
"3011": "Email OTP Verified successfully",
"3012": "Phone OTP Verified successfully",
"3013": "Valid Guardian code",
"3014": "Password has been updated successfully.",
"3015": "Verification code sent on your email.",
"3016": "Send otp on your Email successfully",
"3017": "Profile image update successfully",
"3018": "Task created successfully",
"3019": "Support Email sent successfully",
"3020": "Logged out successfully.",
"3021": "Add junior successfully",
"3022": "Remove junior successfully",
"3023": "Junior is approved successfully",
"3024": "Junior request is rejected successfully",
"3025": "Task is approved successfully",
"3026": "Task is rejected successfully",
"3027": "Article has been created successfully.",
"3028": "Article has been updated successfully.",
"3029": "Article has been deleted successfully.",
"3030": "Article Card has been removed successfully.",
"3031": "Article Survey has been removed successfully.",
"3032": "Task request sent successfully"
}
"""status code error"""
STATUS_CODE_ERROR = {
# Status code for Invalid Input
"4001": ["Invalid input."],
# Status code for Authentication credentials
"4002": ["Authentication credentials were not provided."],
# Status code for Permission
"4003": ["You do not have permission to perform this action."],
# Status code for not found
"4004": ["Not found."],
# Status code for method not allowed
"4005": ["Method not allowed."]
}
-17
View File
@@ -1,17 +0,0 @@
"""
Custom routers for job sourcing .
"""
# third party imports
from rest_framework.routers import DefaultRouter
class OptionalSlashRouter(DefaultRouter):
"""
optional slash router class
"""
def __init__(self):
"""
explicitly appending '/' in urls if '/' doesn't exists for making common url patterns .
"""
super(OptionalSlashRouter, self).__init__()
self.trailing_slash = '/?'
-177
View File
@@ -1,177 +0,0 @@
"""
This file used for file uploaded
"""
import datetime
# python imports
import logging
import mimetypes
import os
import boto3
from django.core.files.storage import FileSystemStorage
# django imports
from django.utils.crypto import get_random_string
from rest_framework.status import HTTP_400_BAD_REQUEST, HTTP_200_OK
from base import constants
from base.constants import NUMBER
# local import
from zod_bank.settings import base_settings as settings
from zod_bank.settings.base_settings import BASE_DIR
def image_upload(folder, file_name, data):
"""
Function to upload files
:param folder:folder location string
:param file_name:file_name without ext string
:param data:data file obj
:return:Dictionary
"""
status = HTTP_400_BAD_REQUEST
img_name = None
error = None
try:
s3_client = boto3.client('s3',
aws_access_key_id=settings.AWS_ACCESS_KEY,
aws_secret_access_key=settings.AWS_SECRET_ACCESS_KEY,
region_name=settings.AWS_DEFAULT_REGION
)
bucket_name = settings.BUCKET_NAME
MEDIA_ROOT = os.path.join(BASE_DIR, 'media/tmp')
fss = FileSystemStorage()
file = fss.save('tmp/' + str(file_name), data)
fss.url(file)
tmp_file = os.path.join(MEDIA_ROOT, str(file_name))
s3_client.upload_file(
tmp_file, bucket_name, folder + str(file_name),
ExtraArgs={'ACL': 'public-read', 'ContentType': data.content_type}
)
os.unlink(tmp_file)
img_name = file_name
status = HTTP_200_OK
except Exception as e:
error = e
logging.error(e)
return status, error, img_name
def file_delete(folder, file_name):
"""
To delete common file
:param folder: folder name str
:param file_name: file_name string type
"""
status = HTTP_400_BAD_REQUEST
error = None
try:
s3_client = boto3.client('s3',
aws_access_key_id=settings.AWS_ACCESS_KEY,
aws_secret_access_key=settings.AWS_SECRET_ACCESS_KEY,
region_name=settings.AWS_DEFAULT_REGION
)
s3_client.delete_object(Bucket=settings.BUCKET_NAME, Key=str(folder) + str(file_name))
status = HTTP_200_OK
except Exception as e:
error = e
return status, error
def get_aws_obj(folder, file_name):
"""
To get aws file obj
:param folder: folder string type
:param file_name: file_name string type
"""
status = HTTP_400_BAD_REQUEST
obj = None
try:
s3_client = boto3.client('s3',
aws_access_key_id=settings.AWS_ACCESS_KEY,
aws_secret_access_key=settings.AWS_SECRET_ACCESS_KEY,
region_name=settings.AWS_DEFAULT_REGION
)
file_name = folder + file_name
obj = s3_client.get_object(Bucket=settings.BUCKET_NAME, Key=file_name)
status = HTTP_200_OK
except Exception as e:
logging.error(e)
return status, obj
def upload_image(post_data, folder):
"""
:param post_data:
:param folder: string type
:return:
"""
upload_obj = None
# Check Post data
if post_data:
date_now = datetime.datetime.now()
file_extension = os.path.splitext(str(post_data.name))
file_extension = file_extension[constants.NUMBER['one']].split(".")[constants.NUMBER['one']].lower()
rand = get_random_string(NUMBER['twelve'])
image_name = str(rand) + date_now.strftime("%s") + "." + file_extension
upload_obj = image_upload(folder, image_name, post_data)
return upload_obj
def upload_voice_kit_image(post_data, folder, image_dir):
"""
:param post_data:
:param folder: string type
:param image_dir: image_dir
:return:
"""
upload_obj = None
# Check Post data
if post_data:
date_now = datetime.datetime.now()
file_extension = os.path.splitext(str(post_data))
file_extension = file_extension[constants.NUMBER['one']].split(".")[constants.NUMBER['one']].lower()
rand = get_random_string(NUMBER['twelve'])
image_name = str(rand) + date_now.strftime("%s") + "." + file_extension
upload_obj = voice_kit_image_upload(folder, image_name, post_data, image_dir)
return upload_obj
def voice_kit_image_upload(folder, file_name, data, image_dir):
"""
Function to upload files
:param folder:folder location string
:param file_name:file_name without ext string
:param data:data file obj
:return:Dictionary
"""
status = HTTP_400_BAD_REQUEST
img_name = None
error = None
try:
s3_client = boto3.client('s3',
aws_access_key_id=settings.AWS_ACCESS_KEY,
aws_secret_access_key=settings.AWS_SECRET_ACCESS_KEY,
region_name=settings.AWS_DEFAULT_REGION
)
bucket_name = settings.BUCKET_NAME
MEDIA_ROOT = os.path.join(BASE_DIR, 'media/tmp')
fss = FileSystemStorage()
with open(image_dir+data, 'rb') as f:
file = fss.save('tmp/' + str(file_name), f)
fss.url(file)
tmp_file = os.path.join(MEDIA_ROOT, str(file_name))
s3_client.upload_file(
tmp_file, bucket_name, folder + str(file_name),
ExtraArgs={'ACL': 'public-read', 'ContentType': mimetypes.guess_type(file_name)[0]}
)
os.unlink(tmp_file)
img_name = file_name
status = HTTP_200_OK
except Exception as e:
error = e
logging.error(e)
return status, error, img_name
-37
View File
@@ -1,37 +0,0 @@
version: '3'
services:
nginx:
image: nginx:latest
container_name: nginx
ports:
- "8000:8000"
volumes:
- ./nginx:/etc/nginx/conf.d
- .:/usr/src/app
depends_on:
- web
web:
build: .
container_name: django
command: bash -c "pip install -r requirements.txt && python manage.py collectstatic --noinput && python manage.py migrate && gunicorn zod_bank.wsgi -b 0.0.0.0:8000 -t 300 --log-level=info"
volumes:
- .:/usr/src/app
broker:
image: rabbitmq:3.7
container_name: rabbitmq
volumes:
- .:/usr/src/app
ports:
- 5673:5673
worker:
build: .
image: celery
container_name: dev_celery
restart: "always"
command: bash -c " celery -A zod_bank.celery worker --concurrency=1 -B -l DEBUG -E"
volumes:
- .:/usr/src/app
depends_on:
- broker
-23
View File
@@ -1,23 +0,0 @@
"""Guardian admin"""
"""Third party Django app"""
from django.contrib import admin
"""Import Django app"""
from .models import Guardian, JuniorTask
# Register your models here.
@admin.register(Guardian)
class GuardianAdmin(admin.ModelAdmin):
"""Junior Admin"""
list_display = ['user', 'family_name']
def __str__(self):
"""Return email id"""
return self.user__email
@admin.register(JuniorTask)
class TaskAdmin(admin.ModelAdmin):
"""Junior Admin"""
list_display = ['id', 'task_name', 'task_status', 'junior', 'due_date', 'points', 'created_at', 'updated_at']
def __str__(self):
"""Return email id"""
return str(self.task_name) + str(self.points)
-9
View File
@@ -1,9 +0,0 @@
"""Guardian app file"""
"""Third party Django app"""
from django.apps import AppConfig
class CustodianConfig(AppConfig):
"""Guardian config"""
default_auto_field = 'django.db.models.BigAutoField'
name = 'guardian'
-43
View File
@@ -1,43 +0,0 @@
# Generated by Django 4.2.2 on 2023-06-23 12:05
from django.conf import settings
import django.contrib.postgres.fields
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
initial = True
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='Guardian',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('country_code', models.IntegerField(blank=True, null=True)),
('phone', models.CharField(blank=True, default=None, max_length=31, null=True)),
('family_name', models.CharField(blank=True, default=None, max_length=50, null=True)),
('gender', models.CharField(blank=True, choices=[('1', 'Male'), ('2', 'Female')], default=None, max_length=15, null=True)),
('dob', models.DateField(blank=True, default=None, max_length=15, null=True)),
('guardian_code', models.CharField(blank=True, default=None, max_length=10, null=True)),
('junior_code', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(blank=True, default=None, max_length=10, null=True), null=True, size=None)),
('referral_code', models.CharField(blank=True, default=None, max_length=10, null=True)),
('referral_code_used', models.CharField(blank=True, default=None, max_length=10, null=True)),
('is_active', models.BooleanField(default=True)),
('is_complete_profile', models.BooleanField(default=False)),
('passcode', models.IntegerField(blank=True, default=None, null=True)),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='guardian_profile', to=settings.AUTH_USER_MODEL, verbose_name='Email')),
],
options={
'verbose_name': 'Guardian',
'db_table': 'guardians',
},
),
]
@@ -1,17 +0,0 @@
# Generated by Django 4.2.2 on 2023-06-27 06:15
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('guardian', '0001_initial'),
]
operations = [
migrations.RemoveField(
model_name='guardian',
name='junior_code',
),
]
@@ -1,18 +0,0 @@
# Generated by Django 4.2.2 on 2023-06-27 13:59
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('guardian', '0002_remove_guardian_junior_code'),
]
operations = [
migrations.AddField(
model_name='guardian',
name='country_name',
field=models.CharField(blank=True, default=None, max_length=30, null=True),
),
]
@@ -1,18 +0,0 @@
# Generated by Django 4.2.2 on 2023-06-28 06:52
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('guardian', '0003_guardian_country_name'),
]
operations = [
migrations.AddField(
model_name='guardian',
name='image',
field=models.ImageField(blank=True, default=None, null=True, upload_to='images/'),
),
]
@@ -1,18 +0,0 @@
# Generated by Django 4.2.2 on 2023-06-29 06:14
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('guardian', '0004_guardian_image'),
]
operations = [
migrations.AlterField(
model_name='guardian',
name='image',
field=models.ImageField(blank=True, default=None, null=True, upload_to=''),
),
]
@@ -1,18 +0,0 @@
# Generated by Django 4.2.2 on 2023-06-29 12:10
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('guardian', '0005_alter_guardian_image'),
]
operations = [
migrations.AddField(
model_name='guardian',
name='is_verified',
field=models.BooleanField(default=False),
),
]
@@ -1,18 +0,0 @@
# Generated by Django 4.2.2 on 2023-06-30 10:53
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('guardian', '0006_guardian_is_verified'),
]
operations = [
migrations.AlterField(
model_name='guardian',
name='country_name',
field=models.CharField(blank=True, default=None, max_length=100, null=True),
),
]
-36
View File
@@ -1,36 +0,0 @@
# Generated by Django 4.2.2 on 2023-07-04 09:24
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('junior', '0006_alter_junior_country_name'),
('guardian', '0007_alter_guardian_country_name'),
]
operations = [
migrations.CreateModel(
name='JuniorTask',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('task_name', models.CharField(max_length=100)),
('task_description', models.CharField(max_length=500)),
('points', models.IntegerField(default=5)),
('due_date', models.DateField(blank=True, null=True)),
('image', models.ImageField(blank=True, default=None, null=True, upload_to='')),
('task_status', models.CharField(blank=True, choices=[('1', 'pending'), ('2', 'in-progress'), ('3', 'rejected'), ('4', 'requested'), ('5', 'completed')], default='pending', max_length=15, null=True)),
('is_active', models.BooleanField(default=True)),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('guardian', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='guardian', to='guardian.guardian', verbose_name='Guardian')),
('junior', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='junior', to='junior.junior', verbose_name='Junior')),
],
options={
'verbose_name': 'Junior Task',
'db_table': 'junior_task',
},
),
]
@@ -1,18 +0,0 @@
# Generated by Django 4.2.2 on 2023-07-04 12:56
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('guardian', '0008_juniortask'),
]
operations = [
migrations.AlterField(
model_name='juniortask',
name='image',
field=models.URLField(blank=True, default=None, null=True),
),
]
@@ -1,18 +0,0 @@
# Generated by Django 4.2.2 on 2023-07-04 13:41
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('guardian', '0009_alter_juniortask_image'),
]
operations = [
migrations.AlterField(
model_name='juniortask',
name='task_status',
field=models.CharField(choices=[('1', 'pending'), ('2', 'in-progress'), ('3', 'rejected'), ('4', 'requested'), ('5', 'completed')], default='pending', max_length=15),
),
]
@@ -1,28 +0,0 @@
# Generated by Django 4.2.2 on 2023-07-05 11:17
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('guardian', '0010_alter_juniortask_task_status'),
]
operations = [
migrations.AddField(
model_name='juniortask',
name='default_image',
field=models.ImageField(blank=True, default=None, null=True, upload_to=''),
),
migrations.AddField(
model_name='juniortask',
name='is_approved',
field=models.BooleanField(default=False),
),
migrations.AlterField(
model_name='juniortask',
name='task_status',
field=models.CharField(choices=[('1', 'pending'), ('2', 'in-progress'), ('3', 'rejected'), ('4', 'requested'), ('5', 'completed')], default=1, max_length=15),
),
]
@@ -1,18 +0,0 @@
# Generated by Django 4.2.2 on 2023-07-06 05:12
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('guardian', '0011_juniortask_default_image_juniortask_is_approved_and_more'),
]
operations = [
migrations.AlterField(
model_name='juniortask',
name='default_image',
field=models.URLField(blank=True, default=None, null=True),
),
]
@@ -1,18 +0,0 @@
# Generated by Django 4.2.2 on 2023-07-06 06:40
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('guardian', '0012_alter_juniortask_default_image'),
]
operations = [
migrations.AlterField(
model_name='guardian',
name='image',
field=models.URLField(blank=True, default=None, null=True),
),
]
@@ -1,18 +0,0 @@
# Generated by Django 4.2.2 on 2023-07-11 11:26
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('guardian', '0013_alter_guardian_image'),
]
operations = [
migrations.AddField(
model_name='guardian',
name='signup_method',
field=models.CharField(choices=[('1', 'manual'), ('2', 'google'), ('3', 'apple')], default='1', max_length=31),
),
]
@@ -1,21 +0,0 @@
# Generated by Django 4.2.2 on 2023-07-14 09:34
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('guardian', '0014_guardian_signup_method'),
]
operations = [
migrations.AlterModelOptions(
name='guardian',
options={'verbose_name': 'Guardian', 'verbose_name_plural': 'Guardian'},
),
migrations.AlterModelOptions(
name='juniortask',
options={'verbose_name': 'Junior Task', 'verbose_name_plural': 'Junior Task'},
),
]
@@ -1,28 +0,0 @@
# Generated by Django 4.2.2 on 2023-07-18 07:17
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('guardian', '0015_alter_guardian_options_alter_juniortask_options'),
]
operations = [
migrations.AddField(
model_name='juniortask',
name='completed_on',
field=models.DateTimeField(blank=True, null=True),
),
migrations.AddField(
model_name='juniortask',
name='rejected_on',
field=models.DateTimeField(blank=True, null=True),
),
migrations.AddField(
model_name='juniortask',
name='requested_on',
field=models.DateTimeField(blank=True, null=True),
),
]
View File
-132
View File
@@ -1,132 +0,0 @@
"""Guardian model file"""
"""Third party Django app"""
from django.db import models
from django.contrib.auth import get_user_model
"""Import Django app"""
from base.constants import GENDERS, TASK_STATUS, PENDING, TASK_POINTS, SIGNUP_METHODS
"""import Junior model"""
from junior.models import Junior
"""Add user model"""
User = get_user_model()
# Create your models here.
# Define junior model with
# various fields like
# phone, country code,
# country name,
# gender,
# date of birth,
# profile image,
# signup method,
# guardian code,
# referral code,
# referral code that used by the guardian
# is invited guardian
# profile is active or not
# profile is complete or not
# passcode
# guardian is verified or not
"""Define junior task model"""
# define name of the Task
# task description
# points of the task
# default image of the task
# image uploaded by junior
# task status
# task approved or not
# Create your models here.
class Guardian(models.Model):
"""Guardian model"""
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='guardian_profile', verbose_name='Email')
"""Contact details"""
country_code = models.IntegerField(blank=True, null=True)
phone = models.CharField(max_length=31, null=True, blank=True, default=None)
"""country name of the guardian"""
country_name = models.CharField(max_length=100, null=True, blank=True, default=None)
"""Image info"""
image = models.URLField(null=True, blank=True, default=None)
"""Personal info"""
family_name = models.CharField(max_length=50, null=True, blank=True, default=None)
"""gender of the guardian"""
gender = models.CharField(choices=GENDERS, max_length=15, null=True, blank=True, default=None)
"""date of birth of the guardian"""
dob = models.DateField(max_length=15, null=True, blank=True, default=None)
"""Profile activity"""
is_active = models.BooleanField(default=True)
"""guardian is verified or not"""
is_verified = models.BooleanField(default=False)
"""guardian profile is complete or not"""
is_complete_profile = models.BooleanField(default=False)
"""passcode of the guardian profile"""
passcode = models.IntegerField(null=True, blank=True, default=None)
"""Sign up method"""
signup_method = models.CharField(max_length=31, choices=SIGNUP_METHODS, default='1')
"""Guardian Codes"""
guardian_code = models.CharField(max_length=10, null=True, blank=True, default=None)
"""Referral code"""
referral_code = models.CharField(max_length=10, null=True, blank=True, default=None)
"""Referral code that is used by guardian while signup"""
referral_code_used = models.CharField(max_length=10, null=True, blank=True, default=None)
"""Profile created and updated time"""
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta(object):
""" Meta class """
db_table = 'guardians'
"""verbose name of the model"""
verbose_name = 'Guardian'
"""change another name"""
verbose_name_plural = 'Guardian'
def __str__(self):
"""Return email id"""
return f'{self.user}'
class JuniorTask(models.Model):
"""Junior Task details model"""
guardian = models.ForeignKey(Guardian, on_delete=models.CASCADE, related_name='guardian', verbose_name='Guardian')
"""task details"""
task_name = models.CharField(max_length=100)
"""task description"""
task_description = models.CharField(max_length=500)
"""points of the task"""
points = models.IntegerField(default=TASK_POINTS)
"""last date of the task"""
due_date = models.DateField(auto_now_add=False, null=True, blank=True)
"""Images of task that is upload by guardian"""
default_image = models.URLField(null=True, blank=True, default=None)
"""image that is uploaded by junior"""
image = models.URLField(null=True, blank=True, default=None)
"""associated junior with the task"""
junior = models.ForeignKey(Junior, on_delete=models.CASCADE, related_name='junior', verbose_name='Junior')
"""task status"""
task_status = models.CharField(choices=TASK_STATUS, max_length=15, default=PENDING)
"""task is active or not"""
is_active = models.BooleanField(default=True)
"""Task is approved or not"""
is_approved = models.BooleanField(default=False)
"""request task on particular date"""
requested_on = models.DateTimeField(auto_now_add=False, null=True, blank=True)
"""reject task on particular date"""
rejected_on = models.DateTimeField(auto_now_add=False, null=True, blank=True)
"""complete task on particular date"""
completed_on = models.DateTimeField(auto_now_add=False, null=True, blank=True)
"""Profile created and updated time"""
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta(object):
""" Meta class """
db_table = 'junior_task'
"""verbose name of the model"""
verbose_name = 'Junior Task'
verbose_name_plural = 'Junior Task'
def __str__(self):
"""Return email id"""
return f'{self.task_name}'
-339
View File
@@ -1,339 +0,0 @@
"""Serializer of Guardian"""
"""Third party Django app"""
import logging
from rest_framework import serializers
# Import Refresh token of jwt
from rest_framework_simplejwt.tokens import RefreshToken
from django.db import transaction
from django.contrib.auth.models import User
from datetime import datetime, time
"""Import Django app"""
# Import guardian's model,
# Import junior's model,
# Import account's model,
# Import constant from
# base package,
# Import messages from
# base package,
# Import some functions
# from utils file"""
from .models import Guardian, JuniorTask
from account.models import UserProfile, UserEmailOtp, UserNotification
from account.utils import generate_code
from junior.serializers import JuniorDetailSerializer
from base.messages import ERROR_CODE, SUCCESS_CODE
from base.constants import NUMBER, JUN, ZOD, GRD
from junior.models import Junior, JuniorPoints
from .utils import real_time, convert_timedelta_into_datetime
# In this serializer file
# define user serializer,
# create guardian serializer,
# task serializer,
# guardian serializer,
# task details serializer,
# top junior serializer,
# guardian profile serializer,
# approve junior serializer,
# approve task serializer,
class UserSerializer(serializers.ModelSerializer):
"""User serializer"""
auth_token = serializers.SerializerMethodField('get_auth_token')
class Meta(object):
"""Meta info"""
model = User
fields = ['id', 'email', 'password', 'auth_token']
def get_auth_token(self, obj):
"""generate auth token"""
refresh = RefreshToken.for_user(obj)
access_token = str(refresh.access_token)
return access_token
def create(self, validated_data):
"""fetch data"""
email = validated_data.get('email')
user_type = self.context
password = validated_data.get('password')
try:
"""Create user profile"""
user = User.objects.create_user(username=email, email=email, password=password)
UserNotification.objects.create(user=user)
if user_type == str(NUMBER['one']):
# create junior profile
Junior.objects.create(auth=user, junior_code=generate_code(JUN, user.id),
referral_code=generate_code(ZOD, user.id))
if user_type == str(NUMBER['two']):
# create guardian profile
Guardian.objects.create(user=user, guardian_code=generate_code(GRD, user.id),
referral_code=generate_code(ZOD, user.id))
return user
except Exception as e:
"""Error handling"""
logging.error(e)
otp = UserEmailOtp.objects.filter(email=email).last()
otp_verified = False
if otp and otp.is_verified:
otp_verified = True
raise serializers.ValidationError({"details":ERROR_CODE['2021'], "otp_verified":bool(otp_verified),
"code": 400, "status":"failed",
})
# update guardian profile
class CreateGuardianSerializer(serializers.ModelSerializer):
"""Create guardian serializer"""
"""Basic info"""
first_name = serializers.SerializerMethodField('get_first_name')
last_name = serializers.SerializerMethodField('get_last_name')
email = serializers.SerializerMethodField('get_email')
"""Contact details"""
phone = serializers.CharField(max_length=20, required=False)
country_code = serializers.IntegerField(required=False)
# basic info
family_name = serializers.CharField(max_length=100, required=False)
dob = serializers.DateField(required=False)
# code info
referral_code = serializers.CharField(max_length=100, required=False)
# image info
image = serializers.URLField(required=False)
class Meta(object):
"""Meta info"""
model = Guardian
fields = ['id', 'first_name', 'last_name', 'email', 'phone', 'family_name', 'gender', 'country_code',
'dob', 'referral_code', 'passcode', 'is_complete_profile', 'referral_code_used',
'country_name', 'image']
def get_first_name(self,obj):
"""first name of guardian"""
return obj.user.first_name
def get_last_name(self,obj):
"""last name of guardian"""
return obj.user.last_name
def get_email(self,obj):
"""emailof guardian"""
return obj.user.email
def create(self, validated_data):
"""Create guardian profile"""
user = User.objects.filter(username=self.context['user']).last()
if user:
"""Save first and last name of guardian"""
if self.context.get('first_name') != '' and self.context.get('first_name') is not None:
user.first_name = self.context.get('first_name')
if self.context.get('last_name') != '' and self.context.get('last_name') is not None:
user.last_name = self.context.get('last_name')
user.save()
"""Create guardian data"""
guardian = Guardian.objects.filter(user=self.context['user']).last()
if guardian:
"""update details according to the data get from request"""
guardian.gender = validated_data.get('gender',guardian.gender)
guardian.family_name = validated_data.get('family_name', guardian.family_name)
guardian.dob = validated_data.get('dob',guardian.dob)
"""Update country code and phone number"""
guardian.phone = validated_data.get('phone', guardian.phone)
guardian.country_code = validated_data.get('country_code', guardian.country_code)
guardian.passcode = validated_data.get('passcode', guardian.passcode)
guardian.country_name = validated_data.get('country_name', guardian.country_name)
guardian.referral_code_used = validated_data.get('referral_code_used', guardian.referral_code_used)
guardian.image = validated_data.get('image', guardian.image)
"""Complete profile of the junior if below all data are filled"""
complete_profile_field = all([guardian.gender, guardian.country_name,
guardian.dob, user.first_name,
guardian.image])
guardian.is_complete_profile = False
if complete_profile_field:
guardian.is_complete_profile = True
guardian.save()
return guardian
def save(self, **kwargs):
"""Save the data into junior table"""
with transaction.atomic():
instance = super().save(**kwargs)
return instance
class TaskSerializer(serializers.ModelSerializer):
"""Task serializer"""
class Meta(object):
"""Meta info"""
model = JuniorTask
fields = ['id', 'task_name','task_description','points', 'due_date', 'junior', 'default_image']
def create(self, validated_data):
"""create default task image data"""
validated_data['guardian'] = Guardian.objects.filter(user=self.context['user']).last()
# update image of the task
images = self.context['image']
validated_data['default_image'] = images
instance = JuniorTask.objects.create(**validated_data)
return instance
class GuardianDetailSerializer(serializers.ModelSerializer):
"""junior serializer"""
email = serializers.SerializerMethodField('get_auth')
first_name = serializers.SerializerMethodField('get_first_name')
last_name = serializers.SerializerMethodField('get_last_name')
def get_auth(self, obj):
"""user email address"""
return obj.user.username
def get_first_name(self, obj):
"""user first name"""
return obj.user.first_name
def get_last_name(self, obj):
"""user last name"""
return obj.user.last_name
class Meta(object):
"""Meta info"""
model = Guardian
fields = ['id', 'email', 'first_name', 'last_name', 'country_code', 'phone', 'gender', 'dob',
'guardian_code', 'referral_code','is_active', 'is_complete_profile', 'created_at', 'image',
'updated_at']
class TaskDetailsSerializer(serializers.ModelSerializer):
"""Task detail serializer"""
junior = JuniorDetailSerializer()
remaining_time = serializers.SerializerMethodField('get_remaining_time')
def get_remaining_time(self, obj):
""" remaining time to complete task"""
due_date_datetime = datetime.combine(obj.due_date, datetime.max.time())
# fetch real time
current_datetime = real_time()
# Perform the subtraction
if due_date_datetime > current_datetime:
time_difference = due_date_datetime - current_datetime
time_only = convert_timedelta_into_datetime(time_difference)
return str(time_difference.days) + ' days ' + str(time_only)
return str(NUMBER['zero']) + ' days ' + '00:00:00:00000'
class Meta(object):
"""Meta info"""
model = JuniorTask
fields = ['id', 'guardian', 'task_name', 'task_description', 'points', 'due_date','default_image', 'image',
'requested_on', 'rejected_on', 'completed_on',
'junior', 'task_status', 'is_active', 'remaining_time', 'created_at','updated_at']
class TopJuniorSerializer(serializers.ModelSerializer):
"""Top junior serializer"""
junior = JuniorDetailSerializer()
position = serializers.IntegerField()
class Meta(object):
"""Meta info"""
model = JuniorPoints
fields = ['id', 'junior', 'total_task_points', 'position', 'created_at', 'updated_at']
def to_representation(self, instance):
"""Convert instance to representation"""
representation = super().to_representation(instance)
representation['position'] = instance.position
return representation
class GuardianProfileSerializer(serializers.ModelSerializer):
"""junior serializer"""
email = serializers.SerializerMethodField('get_auth')
first_name = serializers.SerializerMethodField('get_first_name')
last_name = serializers.SerializerMethodField('get_last_name')
total_count = serializers.SerializerMethodField('get_total_count')
complete_field_count = serializers.SerializerMethodField('get_complete_field_count')
notification_count = serializers.SerializerMethodField('get_notification_count')
def get_auth(self, obj):
"""user email address"""
return obj.user.username
def get_first_name(self, obj):
"""user first name"""
return obj.user.first_name
def get_last_name(self, obj):
"""user last name"""
return obj.user.last_name
def get_total_count(self, obj):
"""total fields count"""
return NUMBER['five']
def get_complete_field_count(self, obj):
"""total filled fields count"""
total_field_list = [obj.user.first_name, obj.country_name,
obj.gender, obj.dob, obj.image]
# count total complete field
total_complete_field = [data for data in total_field_list if data != '' and data is not None]
return len(total_complete_field)
def get_notification_count(self, obj):
"""total notification count"""
return 0
class Meta(object):
"""Meta info"""
model = Guardian
fields = ['id', 'email', 'first_name', 'last_name', 'country_name','country_code', 'phone', 'gender', 'dob',
'guardian_code', 'notification_count', 'total_count', 'complete_field_count', 'referral_code',
'is_active', 'is_complete_profile', 'created_at', 'image', 'signup_method',
'updated_at', 'passcode']
class ApproveJuniorSerializer(serializers.ModelSerializer):
"""approve junior serializer"""
class Meta(object):
"""Meta info"""
model = Junior
fields = ['id', 'guardian_code']
def create(self, validated_data):
"""update guardian code"""
instance = self.context['junior']
instance.guardian_code = [self.context['guardian_code']]
instance.save()
return instance
class ApproveTaskSerializer(serializers.ModelSerializer):
"""approve task serializer"""
class Meta(object):
"""Meta info"""
model = JuniorTask
fields = ['id', 'task_status', 'is_approved']
def create(self, validated_data):
"""update task status """
instance = self.context['task_instance']
junior = self.context['junior']
junior_details = Junior.objects.filter(id=junior).last()
junior_data, created = JuniorPoints.objects.get_or_create(junior=junior_details)
if self.context['action'] == str(NUMBER['one']):
# approve the task
instance.task_status = str(NUMBER['five'])
instance.is_approved = True
# update total task point
junior_data.total_task_points = junior_data.total_task_points + instance.points
# update complete time of task
instance.completed_on = real_time()
else:
# reject the task
instance.task_status = str(NUMBER['three'])
instance.is_approved = False
# update total task point
junior_data.total_task_points = junior_data.total_task_points - instance.points
# update reject time of task
instance.rejected_on = real_time()
instance.save()
junior_data.save()
return instance
-7
View File
@@ -1,7 +0,0 @@
"""task files"""
"""Django import"""
import secrets
def generate_otp():
"""generate random otp"""
digits = "0123456789"
return "".join(secrets.choice(digits) for _ in range(6))
-5
View File
@@ -1,5 +0,0 @@
"""Test file of Guardian"""
"""Third party Django app"""
from django.test import TestCase
# Create your tests here.
-42
View File
@@ -1,42 +0,0 @@
""" Urls files"""
"""Django import"""
from django.urls import path, include
from .views import (SignupViewset, UpdateGuardianProfile, AllTaskListAPIView, CreateTaskAPIView, TaskListAPIView,
SearchTaskListAPIView, TopJuniorListAPIView, ApproveJuniorAPIView, ApproveTaskAPIView)
"""Third party import"""
from rest_framework import routers
"""Define Router"""
router = routers.SimpleRouter()
# API End points with router
# in this file
# we define various api end point
# that is covered in this guardian
# section API:- like
# sign-up, create guardian profile,
# create-task,
# all task list, top junior,
# filter-task"""
"""Sign up API"""
router.register('sign-up', SignupViewset, basename='sign-up')
# Create guardian profile API"""
router.register('create-guardian-profile', UpdateGuardianProfile, basename='update-guardian-profile')
# Create Task API"""
router.register('create-task', CreateTaskAPIView, basename='create-task')
# All Task list API"""
router.register('all-task-list', AllTaskListAPIView, basename='all-task-list')
# Task list bases on the status API"""
router.register('task-list', TaskListAPIView, basename='task-list')
# Leaderboard API"""
router.register('top-junior', TopJuniorListAPIView, basename='top-junior')
# Search Task list on the bases of status, due date, and task title API"""
router.register('filter-task', SearchTaskListAPIView, basename='filter-task')
# Approve junior API"""
router.register('approve-junior', ApproveJuniorAPIView, basename='approve-junior')
# Approve junior API"""
router.register('approve-task', ApproveTaskAPIView, basename='approve-task')
# Define Url pattern"""
urlpatterns = [
path('api/v1/', include(router.urls)),
]
-61
View File
@@ -1,61 +0,0 @@
"""Utiles file of guardian"""
"""Django import"""
import oss2
"""Import setting"""
from django.conf import settings
"""Import tempfile"""
import tempfile
# Import date time module's function
from datetime import datetime, time
# Import real time client module
import ntplib
# import Number constant
from base.constants import NUMBER
# Define upload image on
# ali baba cloud
# firstly save image
# in temporary file
# then check bucket name
# then upload on ali baba
# bucket and reform the image url"""
# fetch real time data without depend on system time
# convert time delta into date time object
def upload_image_to_alibaba(image, filename):
"""upload image on oss alibaba bucket"""
# Save the image object to a temporary file
with tempfile.NamedTemporaryFile(delete=False) as temp_file:
"""write image in temporary file"""
temp_file.write(image.read())
"""auth of bucket"""
auth = oss2.Auth(settings.ALIYUN_OSS_ACCESS_KEY_ID, settings.ALIYUN_OSS_ACCESS_KEY_SECRET)
"""fetch bucket details"""
bucket = oss2.Bucket(auth, settings.ALIYUN_OSS_ENDPOINT, settings.ALIYUN_OSS_BUCKET_NAME)
# Upload the temporary file to Alibaba OSS
bucket.put_object_from_file(filename, temp_file.name)
"""create perfect url for image"""
new_filename = filename.replace(' ', '%20')
return f"https://{settings.ALIYUN_OSS_BUCKET_NAME}.{settings.ALIYUN_OSS_ENDPOINT}/{new_filename}"
def real_time():
""" real time """
# Fetch real time.
# time is not depend on system time
# Get the current datetime
ntp_client = ntplib.NTPClient()
ntp_server = 'pool.ntp.org'
response = ntp_client.request(ntp_server)
current_datetime = datetime.fromtimestamp(response.tx_time)
return current_datetime
def convert_timedelta_into_datetime(time_difference):
"""convert date time"""
# convert timedelta into datetime format
hours = time_difference.seconds // NUMBER['thirty_six_hundred']
minutes = (time_difference.seconds // NUMBER['sixty']) % NUMBER['sixty']
seconds = time_difference.seconds % NUMBER['sixty']
microseconds = time_difference.microseconds
# Create a new time object with the extracted time components
time_only = time(hours, minutes, seconds, microseconds)
return time_only
-283
View File
@@ -1,283 +0,0 @@
"""Views of Guardian"""
# django imports
from rest_framework.permissions import IsAuthenticated
from rest_framework import viewsets, status
from rest_framework.pagination import PageNumberPagination
from django.contrib.auth.models import User
from django.utils import timezone
# Import guardian's model,
# Import junior's model,
# Import account's model,
# Import constant from
# base package,
# Import messages from
# base package,
# Import some functions
# from utils file
# Import account's serializer
# Import account's task
from .serializers import (UserSerializer, CreateGuardianSerializer, TaskSerializer, TaskDetailsSerializer,
TopJuniorSerializer, ApproveJuniorSerializer, ApproveTaskSerializer)
from .models import Guardian, JuniorTask
from junior.models import Junior, JuniorPoints
from account.models import UserEmailOtp, UserNotification
from .tasks import generate_otp
from account.utils import send_otp_email
from account.utils import custom_response, custom_error_response
from base.messages import ERROR_CODE, SUCCESS_CODE
from base.constants import NUMBER
from .utils import upload_image_to_alibaba
from notifications.constants import REGISTRATION
from notifications.utils import send_notification
""" Define APIs """
# Define Signup API,
# update guardian profile,
# list of all task
# list of task according to the status of the task
# create task API
# search task by name of the task API
# top junior API,
# approve junior API
# approve task API
# Create your views here.
# create approve task API
class SignupViewset(viewsets.ModelViewSet):
"""Signup view set"""
queryset = User.objects.all()
serializer_class = UserSerializer
def create(self, request, *args, **kwargs):
"""Create user profile"""
if request.data['user_type'] in ['1', '2']:
serializer = UserSerializer(context=request.data['user_type'], data=request.data)
if serializer.is_valid():
user = serializer.save()
"""Generate otp"""
otp = generate_otp()
expiry = timezone.now() + timezone.timedelta(days=1)
# create user email otp object
UserEmailOtp.objects.create(email=request.data['email'], otp=otp,
user_type=str(request.data['user_type']), expired_at=expiry)
"""Send email to the register user"""
send_otp_email(request.data['email'], otp)
send_notification(REGISTRATION, None, user.id, {})
return custom_response(SUCCESS_CODE['3001'],
response_status=status.HTTP_200_OK)
return custom_error_response(serializer.errors, response_status=status.HTTP_400_BAD_REQUEST)
else:
return custom_error_response(ERROR_CODE['2028'], response_status=status.HTTP_400_BAD_REQUEST)
class UpdateGuardianProfile(viewsets.ViewSet):
"""Update guardian profile"""
queryset = Guardian.objects.all()
serializer_class = CreateGuardianSerializer
permission_classes = [IsAuthenticated]
def create(self, request, *args, **kwargs):
"""Create guardian profile"""
data = request.data
image = request.data.get('image')
image_url = ''
if image:
if image and image.size == NUMBER['zero']:
return custom_error_response(ERROR_CODE['2035'], response_status=status.HTTP_400_BAD_REQUEST)
filename = f"images/{image.name}"
# upload image on ali baba
image_url = upload_image_to_alibaba(image, filename)
data = {"image":image_url}
serializer = CreateGuardianSerializer(context={"user":request.user,
"first_name":request.data.get('first_name'),
"last_name": request.data.get('last_name'),
"image":image_url},
data=data)
if serializer.is_valid():
"""save serializer"""
serializer.save()
return custom_response(None, serializer.data,response_status=status.HTTP_200_OK)
return custom_error_response(serializer.errors, response_status=status.HTTP_400_BAD_REQUEST)
class AllTaskListAPIView(viewsets.ModelViewSet):
"""Update guardian profile"""
serializer_class = TaskDetailsSerializer
queryset = JuniorTask.objects.all()
permission_classes = [IsAuthenticated]
def list(self, request, *args, **kwargs):
"""Create guardian profile"""
queryset = JuniorTask.objects.filter(guardian__user=request.user)
# use TaskDetailsSerializer serializer
serializer = TaskDetailsSerializer(queryset, many=True)
return custom_response(None, serializer.data, response_status=status.HTTP_200_OK)
class TaskListAPIView(viewsets.ModelViewSet):
"""Update guardian profile"""
serializer_class = TaskDetailsSerializer
permission_classes = [IsAuthenticated]
pagination_class = PageNumberPagination
queryset = JuniorTask.objects.all()
def list(self, request, *args, **kwargs):
"""Create guardian profile"""
status_value = self.request.GET.get('status')
search = self.request.GET.get('search')
if search and str(status_value) == '0':
queryset = JuniorTask.objects.filter(guardian__user=request.user,
task_name__icontains=search).order_by('due_date', 'created_at')
elif search and str(status_value) != '0':
queryset = JuniorTask.objects.filter(guardian__user=request.user,task_name__icontains=search,
task_status=status_value).order_by('due_date', 'created_at')
if search is None and str(status_value) == '0':
queryset = JuniorTask.objects.filter(guardian__user=request.user).order_by('due_date', 'created_at')
elif search is None and str(status_value) != '0':
queryset = JuniorTask.objects.filter(guardian__user=request.user,
task_status=status_value).order_by('due_date','created_at')
paginator = self.pagination_class()
# use Pagination
paginated_queryset = paginator.paginate_queryset(queryset, request)
# use TaskDetailsSerializer serializer
serializer = TaskDetailsSerializer(paginated_queryset, many=True)
return custom_response(None, serializer.data, response_status=status.HTTP_200_OK)
class CreateTaskAPIView(viewsets.ModelViewSet):
"""create task for junior"""
serializer_class = TaskSerializer
queryset = JuniorTask.objects.all()
def create(self, request, *args, **kwargs):
image = request.data['default_image']
data = request.data
if 'https' in str(image):
image_data = image
else:
filename = f"images/{image}"
if image and image.size == NUMBER['zero']:
return custom_error_response(ERROR_CODE['2035'], response_status=status.HTTP_400_BAD_REQUEST)
image_url = upload_image_to_alibaba(image, filename)
image_data = image_url
data.pop('default_image')
# use TaskSerializer serializer
serializer = TaskSerializer(context={"user":request.user, "image":image_data}, data=data)
if serializer.is_valid():
# save serializer
serializer.save()
return custom_response(SUCCESS_CODE['3018'], serializer.data, response_status=status.HTTP_200_OK)
return custom_error_response(serializer.errors, response_status=status.HTTP_400_BAD_REQUEST)
class SearchTaskListAPIView(viewsets.ModelViewSet):
"""Update guardian profile"""
serializer_class = TaskDetailsSerializer
permission_classes = [IsAuthenticated]
pagination_class = PageNumberPagination
queryset = JuniorTask.objects.all()
def get_queryset(self):
"""Get the queryset for the view"""
title = self.request.GET.get('title')
# fetch junior query
junior_queryset = JuniorTask.objects.filter(guardian__user=self.request.user, task_name__icontains=title)\
.order_by('due_date', 'created_at')
return junior_queryset
def list(self, request, *args, **kwargs):
"""Create guardian profile"""
queryset = self.get_queryset()
paginator = self.pagination_class()
# use pagination
paginated_queryset = paginator.paginate_queryset(queryset, request)
# use TaskSerializer serializer
serializer = TaskDetailsSerializer(paginated_queryset, many=True)
return custom_response(None, serializer.data, response_status=status.HTTP_200_OK)
class TopJuniorListAPIView(viewsets.ModelViewSet):
"""Top juniors list"""
serializer_class = TopJuniorSerializer
permission_classes = [IsAuthenticated]
queryset = JuniorPoints.objects.all()
def get_serializer_context(self):
# context list
context = super().get_serializer_context()
context.update({'view': self})
return context
def list(self, request, *args, **kwargs):
"""Fetch junior list of those who complete their tasks"""
junior_total_points = self.get_queryset().order_by('-total_task_points')
# Update the position field for each JuniorPoints object
for index, junior in enumerate(junior_total_points):
junior.position = index + 1
junior.save()
serializer = self.get_serializer(junior_total_points, many=True)
return custom_response(None, serializer.data, response_status=status.HTTP_200_OK)
class ApproveJuniorAPIView(viewsets.ViewSet):
"""approve junior by guardian"""
serializer_class = ApproveJuniorSerializer
permission_classes = [IsAuthenticated]
def get_queryset(self):
"""Get the queryset for the view"""
guardian = Guardian.objects.filter(user__email=self.request.user).last()
# fetch junior query
junior_queryset = Junior.objects.filter(id=self.request.data.get('junior_id')).last()
return guardian, junior_queryset
def create(self, request, *args, **kwargs):
""" junior list"""
queryset = self.get_queryset()
# action 1 is use for approve and 2 for reject
if request.data['action'] == '1':
# use ApproveJuniorSerializer serializer
serializer = ApproveJuniorSerializer(context={"guardian_code": queryset[0].guardian_code,
"junior": queryset[1], "action": request.data['action']},
data=request.data)
if serializer.is_valid():
# save serializer
serializer.save()
return custom_response(SUCCESS_CODE['3023'], serializer.data, response_status=status.HTTP_200_OK)
else:
return custom_response(SUCCESS_CODE['3024'], response_status=status.HTTP_200_OK)
class ApproveTaskAPIView(viewsets.ViewSet):
"""approve junior by guardian"""
serializer_class = ApproveTaskSerializer
permission_classes = [IsAuthenticated]
def get_queryset(self):
"""Get the queryset for the view"""
guardian = Guardian.objects.filter(user__email=self.request.user).last()
# task query
task_queryset = JuniorTask.objects.filter(id=self.request.data.get('task_id'),
guardian=guardian,
junior=self.request.data.get('junior_id')).last()
return guardian, task_queryset
def create(self, request, *args, **kwargs):
""" junior list"""
# action 1 is use for approve and 2 for reject
queryset = self.get_queryset()
# use ApproveJuniorSerializer serializer
serializer = ApproveTaskSerializer(context={"guardian_code": queryset[0].guardian_code,
"task_instance": queryset[1],
"action": str(request.data['action']),
"junior": self.request.data['junior_id']},
data=request.data)
if str(request.data['action']) == str(NUMBER['one']) and serializer.is_valid():
# save serializer
serializer.save()
return custom_response(SUCCESS_CODE['3025'], response_status=status.HTTP_200_OK)
elif str(request.data['action']) == str(NUMBER['two']) and serializer.is_valid():
# save serializer
serializer.save()
return custom_response(SUCCESS_CODE['3026'], response_status=status.HTTP_200_OK)
else:
return custom_response(ERROR_CODE['2038'], response_status=status.HTTP_400_BAD_REQUEST)
+35
View File
@@ -0,0 +1,35 @@
import type { Config as JestConfig } from 'jest';
import { pathsToModuleNameMapper } from 'ts-jest';
import { compilerOptions as tsConfigCompilerOptions } from './tsconfig.json';
type ArrayElement<A> = A extends readonly (infer T)[] ? T : never;
type JestProjectConfig = Exclude<ArrayElement<JestConfig['projects']>, string>;
const baseJestConfig: JestConfig = {
// ...
};
const baseJestProjectConfig: JestProjectConfig = {
testEnvironment: 'node',
transform: {
'^.+\\.(t|j)s$': 'ts-jest',
},
moduleNameMapper: pathsToModuleNameMapper(tsConfigCompilerOptions.paths, { prefix: '<rootDir>' }),
globalSetup: '<rootDir>/test/global-setup.ts',
};
const projects: { name?: string; rootDir: string }[] = [{ rootDir: '.' }];
export function buildJestConfig(config?: {
jestConfigs?: JestConfig;
jestProjectConfigs?: JestProjectConfig;
}): JestConfig {
return {
...baseJestConfig,
...config?.jestConfigs,
projects: projects.map(({ name, rootDir }) => ({
...baseJestProjectConfig,
...config?.jestProjectConfigs,
...(name && { displayName: name }),
rootDir,
})),
};
}
+16
View File
@@ -0,0 +1,16 @@
import type { Config } from 'jest';
import { buildJestConfig } from './jest.base.config';
const config: Config = buildJestConfig({
jestProjectConfigs: {
testRegex: '.*\\.spec\\.ts$',
coveragePathIgnorePatterns: ['__testing__', 'entities', '<rootDir>/src/db', 'index.ts'],
},
jestConfigs: {
coverageDirectory: 'coverage',
collectCoverageFrom: ['<rootDir>/src/**/*.(t|j)s'],
coverageReporters: ['clover', 'json', 'lcov', 'text', 'text-summary', 'cobertura'],
},
});
export default config;
View File
-23
View File
@@ -1,23 +0,0 @@
"""Junior admin"""
"""Third party Django app"""
from django.contrib import admin
"""Import Django app"""
from .models import Junior, JuniorPoints
# Register your models here.
@admin.register(Junior)
class JuniorAdmin(admin.ModelAdmin):
"""Junior Admin"""
list_display = ['auth', 'guardian_code']
def __str__(self):
"""Return email id"""
return self.auth__email
@admin.register(JuniorPoints)
class JuniorPointsAdmin(admin.ModelAdmin):
"""Junior Points Admin"""
list_display = ['junior', 'total_task_points', 'position']
def __str__(self):
"""Return email id"""
return self.junior.auth.email
-8
View File
@@ -1,8 +0,0 @@
"""App file"""
"""Import AppConfig"""
from django.apps import AppConfig
class JuniorConfig(AppConfig):
"""Junior config"""
default_auto_field = 'django.db.models.BigAutoField'
name = 'junior'
-42
View File
@@ -1,42 +0,0 @@
# Generated by Django 4.2.2 on 2023-06-23 12:05
from django.conf import settings
import django.contrib.postgres.fields
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
initial = True
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='Junior',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('phone', models.CharField(blank=True, default=None, max_length=31, null=True)),
('country_code', models.IntegerField(blank=True, null=True)),
('gender', models.CharField(blank=True, choices=[('1', 'Male'), ('2', 'Female')], default=None, max_length=10, null=True)),
('dob', models.DateField(blank=True, default=None, max_length=15, null=True)),
('junior_code', models.CharField(blank=True, default=None, max_length=10, null=True)),
('guardian_code', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(blank=True, default=None, max_length=10, null=True), null=True, size=None)),
('referral_code', models.CharField(blank=True, default=None, max_length=10, null=True)),
('referral_code_used', models.CharField(blank=True, default=None, max_length=10, null=True)),
('is_active', models.BooleanField(default=True)),
('is_complete_profile', models.BooleanField(default=False)),
('passcode', models.IntegerField(blank=True, default=None, null=True)),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('auth', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='junior_profile', to=settings.AUTH_USER_MODEL, verbose_name='Email')),
],
options={
'verbose_name': 'Junior',
'db_table': 'junior',
},
),
]
@@ -1,18 +0,0 @@
# Generated by Django 4.2.2 on 2023-06-27 13:59
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('junior', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='junior',
name='country_name',
field=models.CharField(blank=True, default=None, max_length=30, null=True),
),
]
-18
View File
@@ -1,18 +0,0 @@
# Generated by Django 4.2.2 on 2023-06-28 10:16
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('junior', '0002_junior_country_name'),
]
operations = [
migrations.AddField(
model_name='junior',
name='image',
field=models.ImageField(blank=True, default=None, null=True, upload_to='images/'),
),
]
@@ -1,18 +0,0 @@
# Generated by Django 4.2.2 on 2023-06-29 06:14
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('junior', '0003_junior_image'),
]
operations = [
migrations.AlterField(
model_name='junior',
name='image',
field=models.ImageField(blank=True, default=None, null=True, upload_to=''),
),
]
@@ -1,18 +0,0 @@
# Generated by Django 4.2.2 on 2023-06-29 12:10
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('junior', '0004_alter_junior_image'),
]
operations = [
migrations.AddField(
model_name='junior',
name='is_verified',
field=models.BooleanField(default=False),
),
]
@@ -1,18 +0,0 @@
# Generated by Django 4.2.2 on 2023-06-30 10:53
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('junior', '0005_junior_is_verified'),
]
operations = [
migrations.AlterField(
model_name='junior',
name='country_name',
field=models.CharField(blank=True, default=None, max_length=100, null=True),
),
]
@@ -1,18 +0,0 @@
# Generated by Django 4.2.2 on 2023-07-06 12:18
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('junior', '0006_alter_junior_country_name'),
]
operations = [
migrations.AlterField(
model_name='junior',
name='image',
field=models.URLField(blank=True, default=None, null=True),
),
]
-28
View File
@@ -1,28 +0,0 @@
# Generated by Django 4.2.2 on 2023-07-09 12:40
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('junior', '0007_alter_junior_image'),
]
operations = [
migrations.CreateModel(
name='JuniorPoints',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('total_task_points', models.IntegerField(blank=True, default=0, null=True)),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('junior', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='junior_points', to='junior.junior')),
],
options={
'verbose_name': 'Junior Task Points',
'db_table': 'junior_task_points',
},
),
]
@@ -1,18 +0,0 @@
# Generated by Django 4.2.2 on 2023-07-10 07:36
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('junior', '0008_juniorpoints'),
]
operations = [
migrations.AddField(
model_name='juniorpoints',
name='position',
field=models.IntegerField(blank=True, default=99999, null=True),
),
]
@@ -1,18 +0,0 @@
# Generated by Django 4.2.2 on 2023-07-11 11:26
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('junior', '0009_juniorpoints_position'),
]
operations = [
migrations.AddField(
model_name='junior',
name='signup_method',
field=models.CharField(choices=[('1', 'manual'), ('2', 'google'), ('3', 'apple')], default='1', max_length=31),
),
]
@@ -1,18 +0,0 @@
# Generated by Django 4.2.2 on 2023-07-12 06:52
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('junior', '0010_junior_signup_method'),
]
operations = [
migrations.AddField(
model_name='junior',
name='relationship',
field=models.CharField(blank=True, choices=[('1', 'parent'), ('2', 'legal_guardian')], default='1', max_length=31, null=True),
),
]
@@ -1,18 +0,0 @@
# Generated by Django 4.2.2 on 2023-07-12 10:51
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('junior', '0011_junior_relationship'),
]
operations = [
migrations.AddField(
model_name='junior',
name='is_invited',
field=models.BooleanField(default=False),
),
]
@@ -1,21 +0,0 @@
# Generated by Django 4.2.2 on 2023-07-14 09:34
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('junior', '0012_junior_is_invited'),
]
operations = [
migrations.AlterModelOptions(
name='junior',
options={'verbose_name': 'Junior', 'verbose_name_plural': 'Junior'},
),
migrations.AlterModelOptions(
name='juniorpoints',
options={'verbose_name': 'Junior Task Points', 'verbose_name_plural': 'Junior Task Points'},
),
]
@@ -1,18 +0,0 @@
# Generated by Django 4.2.2 on 2023-07-18 09:28
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('junior', '0013_alter_junior_options_alter_juniorpoints_options'),
]
operations = [
migrations.AddField(
model_name='junior',
name='is_password_set',
field=models.BooleanField(default=True),
),
]
View File
-109
View File
@@ -1,109 +0,0 @@
"""Junior model """
"""Import django"""
from django.db import models
"""Import get_user_model function"""
from django.contrib.auth import get_user_model
"""Import ArrayField"""
from django.contrib.postgres.fields import ArrayField
"""Import django app"""
from base.constants import GENDERS, SIGNUP_METHODS, RELATIONSHIP
"""Define User model"""
User = get_user_model()
# Create your models here.
# Define junior model with
# various fields like
# phone, country code,
# country name,
# gender,
# date of birth,
# profile image,
# relationship type of the guardian
# signup method,
# guardian code,
# junior code,
# referral code,
# referral code that used by the junior
# is invited junior
# profile is active or not
# profile is complete or not
# passcode
# junior is verified or not
"""Define junior points model"""
# points of the junior
# position of the junior
class Junior(models.Model):
"""Junior model"""
auth = models.ForeignKey(User, on_delete=models.CASCADE, related_name='junior_profile', verbose_name='Email')
# Contact details"""
phone = models.CharField(max_length=31, null=True, blank=True, default=None)
country_code = models.IntegerField(blank=True, null=True)
# country name of the guardian"""
country_name = models.CharField(max_length=100, null=True, blank=True, default=None)
# Personal info"""
gender = models.CharField(max_length=10, choices=GENDERS, null=True, blank=True, default=None)
# Date of birth"""
dob = models.DateField(max_length=15, null=True, blank=True, default=None)
# Image of the junior"""
image = models.URLField(null=True, blank=True, default=None)
# relationship"""
relationship = models.CharField(max_length=31, choices=RELATIONSHIP, null=True, blank=True,
default='1')
# Sign up method"""
signup_method = models.CharField(max_length=31, choices=SIGNUP_METHODS, default='1')
# Codes"""
junior_code = models.CharField(max_length=10, null=True, blank=True, default=None)
# Guardian Codes"""
guardian_code = ArrayField(models.CharField(max_length=10, null=True, blank=True, default=None),null=True)
# Referral code"""
referral_code = models.CharField(max_length=10, null=True, blank=True, default=None)
# Referral code that is used by junior while signup"""
referral_code_used = models.CharField(max_length=10, null=True, blank=True, default=None)
# invited junior"""
is_invited = models.BooleanField(default=False)
# Profile activity"""
is_active = models.BooleanField(default=True)
# check password is set or not
is_password_set = models.BooleanField(default=True)
# junior profile is complete or not"""
is_complete_profile = models.BooleanField(default=False)
# passcode of the junior profile"""
passcode = models.IntegerField(null=True, blank=True, default=None)
# junior is verified or not"""
is_verified = models.BooleanField(default=False)
# Profile created and updated time"""
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta(object):
""" Meta class """
db_table = 'junior'
verbose_name = 'Junior'
# another name of the model"""
verbose_name_plural = 'Junior'
def __str__(self):
"""Return email id"""
return f'{self.auth}'
class JuniorPoints(models.Model):
"""Junior model"""
junior = models.OneToOneField(Junior, on_delete=models.CASCADE, related_name='junior_points')
# Contact details"""
total_task_points = models.IntegerField(blank=True, null=True, default=0)
# position of the junior"""
position = models.IntegerField(blank=True, null=True, default=99999)
# Profile created and updated time"""
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta(object):
""" Meta class """
db_table = 'junior_task_points'
verbose_name = 'Junior Task Points'
# another name of the model"""
verbose_name_plural = 'Junior Task Points'
def __str__(self):
"""Return email id"""
return f'{self.junior.auth}'
-373
View File
@@ -1,373 +0,0 @@
"""Serializer file for junior"""
"""Import Django 3rd party app"""
from rest_framework import serializers
from django.contrib.auth.models import User
from django.db import transaction
from datetime import datetime
from django.utils import timezone
from rest_framework_simplejwt.tokens import RefreshToken
"""Import django app"""
from account.utils import send_otp_email, generate_code
from junior.models import Junior, JuniorPoints
from guardian.tasks import generate_otp
from base.messages import ERROR_CODE, SUCCESS_CODE
from base.constants import PENDING, IN_PROGRESS, REJECTED, REQUESTED, COMPLETED, NUMBER, JUN, ZOD
from guardian.models import Guardian, JuniorTask
from account.models import UserEmailOtp
from junior.utils import junior_notification_email, junior_approval_mail
from guardian.utils import real_time
class ListCharField(serializers.ListField):
"""Serializer for Array field"""
child = serializers.CharField()
def to_representation(self, data):
"""to represent the data"""
return data
def to_internal_value(self, data):
"""internal value"""
if isinstance(data, list):
return data
raise serializers.ValidationError({"details":ERROR_CODE['2025']})
class CreateJuniorSerializer(serializers.ModelSerializer):
"""Create junior serializer"""
first_name = serializers.SerializerMethodField('get_first_name')
last_name = serializers.SerializerMethodField('get_last_name')
email = serializers.SerializerMethodField('get_email')
phone = serializers.CharField(max_length=20, required=False)
country_code = serializers.IntegerField(required=False)
dob = serializers.DateField(required=False)
referral_code = serializers.CharField(max_length=100, required=False)
guardian_code = ListCharField(required=False)
image = serializers.URLField(required=False)
class Meta(object):
"""Meta info"""
model = Junior
fields = ['id', 'first_name', 'last_name', 'email', 'phone', 'gender', 'country_code', 'dob', 'referral_code',
'passcode', 'is_complete_profile', 'guardian_code', 'referral_code_used',
'country_name', 'image', 'is_invited']
def get_first_name(self,obj):
"""first name of junior"""
return obj.auth.first_name
def get_last_name(self,obj):
"""last name of junior"""
return obj.auth.last_name
def get_email(self,obj):
"""email of junior"""
return obj.auth.email
def create(self, validated_data):
"""Create junior profile"""
guardian_code = validated_data.get('guardian_code',None)
user = User.objects.filter(username=self.context['user']).last()
if user:
"""Save first and last name of junior"""
if self.context.get('first_name') != '' and self.context.get('first_name') is not None:
user.first_name = self.context.get('first_name')
if self.context.get('last_name') != '' and self.context.get('last_name') is not None:
user.last_name = self.context.get('last_name')
user.save()
"""Create junior data"""
junior = Junior.objects.filter(auth=self.context['user']).last()
if junior:
"""update details according to the data get from request"""
junior.gender = validated_data.get('gender',junior.gender)
"""Update guardian code"""
junior.guardian_code = validated_data.get('guardian_code', junior.guardian_code)
"""condition for guardian code"""
if guardian_code:
junior.guardian_code = guardian_code
junior.dob = validated_data.get('dob',junior.dob)
junior.passcode = validated_data.get('passcode', junior.passcode)
junior.country_name = validated_data.get('country_name', junior.country_name)
"""Update country code and phone number"""
junior.phone = validated_data.get('phone', junior.phone)
junior.country_code = validated_data.get('country_code', junior.country_code)
junior.referral_code_used = validated_data.get('referral_code_used', junior.referral_code_used)
junior.image = validated_data.get('image', junior.image)
"""Complete profile of the junior if below all data are filled"""
complete_profile_field = all([junior.gender, junior.country_name, junior.image,
junior.dob, user.first_name])
junior.is_complete_profile = False
if complete_profile_field:
junior.is_complete_profile = True
junior.save()
return junior
def save(self, **kwargs):
"""Save the data into junior table"""
with transaction.atomic():
instance = super().save(**kwargs)
return instance
class JuniorDetailSerializer(serializers.ModelSerializer):
"""junior serializer"""
email = serializers.SerializerMethodField('get_auth')
first_name = serializers.SerializerMethodField('get_first_name')
last_name = serializers.SerializerMethodField('get_last_name')
def get_auth(self, obj):
"""user email address"""
return obj.auth.username
def get_first_name(self, obj):
"""user first name"""
return obj.auth.first_name
def get_last_name(self, obj):
"""user last name"""
return obj.auth.last_name
class Meta(object):
"""Meta info"""
model = Junior
fields = ['id', 'email', 'first_name', 'last_name', 'country_code', 'phone', 'gender', 'dob',
'guardian_code', 'is_invited', 'referral_code','is_active', 'is_complete_profile', 'created_at',
'image', 'updated_at']
class JuniorDetailListSerializer(serializers.ModelSerializer):
"""junior serializer"""
email = serializers.SerializerMethodField('get_auth')
first_name = serializers.SerializerMethodField('get_first_name')
last_name = serializers.SerializerMethodField('get_last_name')
assigned_task = serializers.SerializerMethodField('get_assigned_task')
points = serializers.SerializerMethodField('get_points')
in_progress_task = serializers.SerializerMethodField('get_in_progress_task')
completed_task = serializers.SerializerMethodField('get_completed_task')
requested_task = serializers.SerializerMethodField('get_requested_task')
rejected_task = serializers.SerializerMethodField('get_rejected_task')
pending_task = serializers.SerializerMethodField('get_pending_task')
position = serializers.SerializerMethodField('get_position')
def get_auth(self, obj):
return obj.auth.username
def get_first_name(self, obj):
return obj.auth.first_name
def get_last_name(self, obj):
return obj.auth.last_name
def get_assigned_task(self, obj):
data = JuniorTask.objects.filter(junior=obj).count()
return data
def get_position(self, obj):
data = JuniorPoints.objects.filter(junior=obj).last()
if data:
return data.position
return 99999
def get_points(self, obj):
data = sum(JuniorTask.objects.filter(junior=obj, task_status=COMPLETED).values_list('points', flat=True))
return data
def get_in_progress_task(self, obj):
data = JuniorTask.objects.filter(junior=obj, task_status=IN_PROGRESS).count()
return data
def get_completed_task(self, obj):
data = JuniorTask.objects.filter(junior=obj, task_status=COMPLETED).count()
return data
def get_requested_task(self, obj):
data = JuniorTask.objects.filter(junior=obj, task_status=REQUESTED).count()
return data
def get_rejected_task(self, obj):
data = JuniorTask.objects.filter(junior=obj, task_status=REJECTED).count()
return data
def get_pending_task(self, obj):
data = JuniorTask.objects.filter(junior=obj, task_status=PENDING).count()
return data
class Meta(object):
"""Meta info"""
model = Junior
fields = ['id', 'email', 'first_name', 'last_name', 'country_code', 'phone', 'gender', 'dob',
'guardian_code', 'referral_code','is_active', 'is_complete_profile', 'created_at', 'image',
'updated_at', 'assigned_task','points', 'pending_task', 'in_progress_task', 'completed_task',
'requested_task', 'rejected_task', 'position', 'is_invited']
class JuniorProfileSerializer(serializers.ModelSerializer):
"""junior serializer"""
email = serializers.SerializerMethodField('get_auth')
first_name = serializers.SerializerMethodField('get_first_name')
last_name = serializers.SerializerMethodField('get_last_name')
notification_count = serializers.SerializerMethodField('get_notification_count')
total_count = serializers.SerializerMethodField('get_total_count')
complete_field_count = serializers.SerializerMethodField('get_complete_field_count')
def get_auth(self, obj):
"""user email address"""
return obj.auth.username
def get_first_name(self, obj):
"""user first name"""
return obj.auth.first_name
def get_last_name(self, obj):
"""user last name"""
return obj.auth.last_name
def get_notification_count(self, obj):
"""total notification count"""
return 0
def get_total_count(self, obj):
"""total fields count"""
return NUMBER['five']
def get_complete_field_count(self, obj):
"""total filled fields count"""
field_list = [obj.auth.first_name, obj.country_name,
obj.gender, obj.dob, obj.image]
complete_field = [data for data in field_list if data is not None and data != '']
return len(complete_field)
class Meta(object):
"""Meta info"""
model = Junior
fields = ['id', 'email', 'first_name', 'last_name', 'country_name', 'country_code', 'phone', 'gender', 'dob',
'guardian_code', 'referral_code','is_active', 'is_complete_profile', 'created_at', 'image',
'updated_at', 'notification_count', 'total_count', 'complete_field_count', 'signup_method',
'is_invited', 'passcode']
class AddJuniorSerializer(serializers.ModelSerializer):
"""Add junior serializer"""
auth_token = serializers.SerializerMethodField('get_auth_token')
def get_auth_token(self, obj):
"""auth token"""
refresh = RefreshToken.for_user(obj)
access_token = str(refresh.access_token)
return access_token
class Meta(object):
"""Meta info"""
model = Junior
fields = ['id', 'gender','dob', 'relationship', 'auth_token', 'is_invited']
def create(self, validated_data):
""" create junior"""
with transaction.atomic():
email = self.context['email']
guardian = self.context['user']
full_name = self.context['first_name'] + ' ' + self.context['last_name']
guardian_data = Guardian.objects.filter(user__username=guardian).last()
user_data = User.objects.create(username=email, email=email,
first_name=self.context['first_name'],
last_name=self.context['last_name'])
password = User.objects.make_random_password()
user_data.set_password(password)
user_data.save()
junior_data = Junior.objects.create(auth=user_data, gender=validated_data.get('gender'),
dob=validated_data.get('dob'), is_invited=True,
relationship=validated_data.get('relationship'),
junior_code=generate_code(JUN, user_data.id),
referral_code=generate_code(ZOD, user_data.id),
referral_code_used=guardian_data.referral_code,
is_password_set=False, is_verified=True)
"""Generate otp"""
otp_value = generate_otp()
expiry_time = timezone.now() + timezone.timedelta(days=1)
UserEmailOtp.objects.create(email=email, otp=otp_value,
user_type='1', expired_at=expiry_time, is_verified=True)
"""Send email to the register user"""
send_otp_email(email, otp_value)
"""Notification email"""
junior_notification_email(email, full_name, email, password)
junior_approval_mail(guardian, full_name)
return junior_data
class RemoveJuniorSerializer(serializers.ModelSerializer):
"""User Update Serializer"""
class Meta(object):
"""Meta class"""
model = Junior
fields = ('id', 'is_invited')
def update(self, instance, validated_data):
if instance:
instance.is_invited = False
instance.guardian_code = '{}'
instance.save()
return instance
class CompleteTaskSerializer(serializers.ModelSerializer):
"""User task Serializer"""
class Meta(object):
"""Meta class"""
model = JuniorTask
fields = ('id', 'image')
def update(self, instance, validated_data):
instance.image = validated_data.get('image', instance.image)
instance.requested_on = real_time()
instance.task_status = str(NUMBER['four'])
instance.is_approved = False
instance.save()
return instance
class JuniorPointsSerializer(serializers.ModelSerializer):
"""Junior points serializer"""
junior_id = serializers.SerializerMethodField('get_junior_id')
total_points = serializers.SerializerMethodField('get_points')
in_progress_task = serializers.SerializerMethodField('get_in_progress_task')
completed_task = serializers.SerializerMethodField('get_completed_task')
requested_task = serializers.SerializerMethodField('get_requested_task')
rejected_task = serializers.SerializerMethodField('get_rejected_task')
pending_task = serializers.SerializerMethodField('get_pending_task')
position = serializers.SerializerMethodField('get_position')
def get_junior_id(self, obj):
"""junior id"""
return obj.junior.id
def get_position(self, obj):
data = JuniorPoints.objects.filter(junior=obj.junior).last()
if data:
return data.position
return 99999
def get_points(self, obj):
"""total points"""
points = JuniorPoints.objects.filter(junior=obj.junior).last()
if points:
return points.total_task_points
def get_in_progress_task(self, obj):
return JuniorTask.objects.filter(junior=obj.junior, task_status=IN_PROGRESS).count()
def get_completed_task(self, obj):
return JuniorTask.objects.filter(junior=obj.junior, task_status=COMPLETED).count()
def get_requested_task(self, obj):
return JuniorTask.objects.filter(junior=obj.junior, task_status=REQUESTED).count()
def get_rejected_task(self, obj):
return JuniorTask.objects.filter(junior=obj.junior, task_status=REJECTED).count()
def get_pending_task(self, obj):
return JuniorTask.objects.filter(junior=obj.junior, task_status=PENDING).count()
class Meta(object):
"""Meta info"""
model = Junior
fields = ['junior_id', 'total_points', 'position', 'pending_task', 'in_progress_task', 'completed_task',
'requested_task', 'rejected_task']
-5
View File
@@ -1,5 +0,0 @@
"""Junior test file"""
"""Import TestCase"""
from django.test import TestCase
# Create your tests here.
-44
View File
@@ -1,44 +0,0 @@
""" Urls files"""
"""Django import"""
from django.urls import path, include
from .views import (UpdateJuniorProfile, ValidateGuardianCode, JuniorListAPIView, AddJuniorAPIView,
InvitedJuniorAPIView, FilterJuniorAPIView, RemoveJuniorAPIView, JuniorTaskListAPIView,
CompleteJuniorTaskAPIView, JuniorPointsListAPIView)
"""Third party import"""
from rest_framework import routers
"""Router"""
router = routers.SimpleRouter()
# API End points with router
# in this file
# we define various api end point
# that is covered in this guardian
# section API:- like
# Create junior profile API, validate junior profile,
# junior list,
# add junior list, invited junior,
# filter-junior,
# remove junior,
# junior task list
"""API End points with router"""
router.register('create-junior-profile', UpdateJuniorProfile, basename='profile-update')
# validate guardian code API"""
router.register('validate-guardian-code', ValidateGuardianCode, basename='validate-guardian-code')
# junior list API"""
router.register('junior-list', JuniorListAPIView, basename='junior-list')
# Add junior list API"""
router.register('add-junior', AddJuniorAPIView, basename='add-junior')
# Invited junior list API"""
router.register('invited-junior', InvitedJuniorAPIView, basename='invited-junior')
# Filter junior list API"""
router.register('filter-junior', FilterJuniorAPIView, basename='filter-junior')
# junior's task list API"""
router.register('junior-task-list', JuniorTaskListAPIView, basename='junior-task-list')
# junior's task list API"""
router.register('junior-points', JuniorPointsListAPIView, basename='junior-points')
# Define url pattern"""
urlpatterns = [
path('api/v1/', include(router.urls)),
path('api/v1/remove-junior/', RemoveJuniorAPIView.as_view()),
path('api/v1/complete-task/', CompleteJuniorTaskAPIView.as_view())
]
-47
View File
@@ -1,47 +0,0 @@
"""Account utils"""
"""Import django"""
from django.conf import settings
"""Third party Django app"""
from templated_email import send_templated_mail
# junior notification
# email for sending email
# when guardian create junior profile
# guardian get email when junior send
# request for approving the profile and
# being part of the zod bank and access the platform
# define junior notification email
# junior approval email
def junior_notification_email(recipient_email, full_name, email, password):
"""Notification email"""
from_email = settings.EMAIL_FROM_ADDRESS
# recipient email"""
recipient_list = [recipient_email]
# use send template mail for sending email"""
send_templated_mail(
template_name='junior_notification_email.email',
from_email=from_email,
recipient_list=recipient_list,
context={
'full_name': full_name,
'url':'link',
'email': email,
'password': password
}
)
return full_name
def junior_approval_mail(guardian, full_name):
"""junior approval mail"""
from_email = settings.EMAIL_FROM_ADDRESS
recipient_list = [guardian]
# use send template mail for sending email"""
send_templated_mail(
template_name='junior_approval_mail.email',
from_email=from_email,
recipient_list=recipient_list,
context={
'full_name': full_name
}
)
return full_name
-274
View File
@@ -1,274 +0,0 @@
"""Junior view file"""
import os
from rest_framework import viewsets, status, generics,views
from rest_framework.permissions import IsAuthenticated
from rest_framework.pagination import PageNumberPagination
import requests
"""Django app import"""
# Import guardian's model,
# Import junior's model,
# Import account's model,
# Import constant from
# base package,
# Import messages from
# base package,
# Import some functions
# from utils file
# Import account's serializer
# Import account's task
from junior.models import Junior
from .serializers import (CreateJuniorSerializer, JuniorDetailListSerializer, AddJuniorSerializer,\
RemoveJuniorSerializer, CompleteTaskSerializer, JuniorPointsSerializer)
from guardian.models import Guardian, JuniorTask
from guardian.serializers import TaskDetailsSerializer
from base.messages import ERROR_CODE, SUCCESS_CODE
from base.constants import NUMBER
from account.utils import custom_response, custom_error_response
from guardian.utils import upload_image_to_alibaba
""" Define APIs """
# Define validate guardian code API,
# update junior profile,
# list of all assosicated junior
# Add junior API
# invite junior API
# search junior API
# remove junior API,
# approve junior API
# Create your views here.
class UpdateJuniorProfile(viewsets.ViewSet):
"""Update junior profile"""
queryset = Junior.objects.all()
serializer_class = CreateJuniorSerializer
permission_classes = [IsAuthenticated]
def create(self, request, *args, **kwargs):
"""Use CreateJuniorSerializer"""
request_data = request.data
image = request.data.get('image')
image_url = ''
if image:
if image.size == NUMBER['zero']:
return custom_error_response(ERROR_CODE['2035'], response_status=status.HTTP_400_BAD_REQUEST)
filename = f"images/{image.name}"
# upload image on ali baba
image_url = upload_image_to_alibaba(image, filename)
request_data = {"image": image_url}
serializer = CreateJuniorSerializer(context={"user":request.user, "image":image_url,
"first_name": request.data.get('first_name'),
"last_name": request.data.get('last_name')
},
data=request_data)
if serializer.is_valid():
"""save serializer"""
serializer.save()
return custom_response(None, serializer.data, response_status=status.HTTP_200_OK)
return custom_error_response(serializer.errors, response_status=status.HTTP_400_BAD_REQUEST)
class ValidateGuardianCode(viewsets.ViewSet):
"""Check guardian code exist or not"""
queryset = Guardian.objects.all()
permission_classes = [IsAuthenticated]
def list(self, request, *args, **kwargs):
"""check guardian code"""
guardian_code = self.request.GET.get('guardian_code').split(',')
for code in guardian_code:
# fetch guardian object
guardian_data = Guardian.objects.filter(guardian_code=code).exists()
if guardian_data:
return custom_response(SUCCESS_CODE['3013'], response_status=status.HTTP_200_OK)
else:
return custom_error_response(ERROR_CODE["2022"], response_status=status.HTTP_400_BAD_REQUEST)
class JuniorListAPIView(viewsets.ModelViewSet):
"""Junior list of assosicated guardian"""
serializer_class = JuniorDetailListSerializer
queryset = Junior.objects.all()
permission_classes = [IsAuthenticated]
def list(self, request, *args, **kwargs):
""" junior list"""
auth_token = self.request.META['HTTP_AUTHORIZATION']
headers_token = {
'Authorization': auth_token
}
requests.get(os.getenv('BASE_URL') + '/api/v1/top-junior/', headers=headers_token)
guardian_data = Guardian.objects.filter(user__email=request.user).last()
queryset = Junior.objects.filter(guardian_code__icontains=str(guardian_data.guardian_code))
# use JuniorDetailListSerializer serializer
serializer = JuniorDetailListSerializer(queryset, many=True)
return custom_response(None, serializer.data, response_status=status.HTTP_200_OK)
class AddJuniorAPIView(viewsets.ModelViewSet):
"""Add Junior by guardian"""
queryset = Junior.objects.all()
serializer_class = AddJuniorSerializer
permission_classes = [IsAuthenticated]
def create(self, request, *args, **kwargs):
""" junior list"""
info = {'user': request.user, 'email': request.data['email'], 'first_name': request.data['first_name'],
'last_name': request.data['last_name']}
# use AddJuniorSerializer serializer
serializer = AddJuniorSerializer(data=request.data, context=info)
if serializer.is_valid():
# save serializer
serializer.save()
return custom_response(SUCCESS_CODE['3021'], serializer.data, response_status=status.HTTP_200_OK)
return custom_error_response(serializer.error, response_status=status.HTTP_400_BAD_REQUEST)
class InvitedJuniorAPIView(viewsets.ModelViewSet):
"""Junior list of assosicated guardian"""
serializer_class = JuniorDetailListSerializer
queryset = Junior.objects.all()
permission_classes = [IsAuthenticated]
pagination_class = PageNumberPagination
def get_queryset(self):
"""Get the queryset for the view"""
guardian = Guardian.objects.filter(user__email=self.request.user).last()
junior_queryset = Junior.objects.filter(guardian_code__icontains=str(guardian.guardian_code),
is_invited=True)
return junior_queryset
def list(self, request, *args, **kwargs):
""" junior list"""
queryset = self.get_queryset()
paginator = self.pagination_class()
paginated_queryset = paginator.paginate_queryset(queryset, request)
# use JuniorDetailListSerializer serializer
serializer = JuniorDetailListSerializer(paginated_queryset, many=True)
return custom_response(None, serializer.data, response_status=status.HTTP_200_OK)
class FilterJuniorAPIView(viewsets.ModelViewSet):
"""Update guardian profile"""
serializer_class = JuniorDetailListSerializer
permission_classes = [IsAuthenticated]
pagination_class = PageNumberPagination
queryset = Junior.objects.all()
def get_queryset(self):
"""Get the queryset for the view"""
title = self.request.GET.get('title')
guardian_data = Guardian.objects.filter(user__email=self.request.user).last()
queryset = Junior.objects.filter(guardian_code__icontains=str(guardian_data.guardian_code),
is_invited=True, auth__first_name=title)
return queryset
def list(self, request, *args, **kwargs):
"""Create guardian profile"""
queryset = self.get_queryset()
paginator = self.pagination_class()
# use Pagination
paginated_queryset = paginator.paginate_queryset(queryset, request)
# use JuniorDetailListSerializer serializer
serializer = JuniorDetailListSerializer(paginated_queryset, many=True)
return custom_response(None, serializer.data, response_status=status.HTTP_200_OK)
class RemoveJuniorAPIView(views.APIView):
"""Remove junior API"""
serializer_class = RemoveJuniorSerializer
model = Junior
permission_classes = [IsAuthenticated]
def put(self, request, format=None):
junior_id = self.request.GET.get('id')
guardian = Guardian.objects.filter(user__email=self.request.user).last()
# fetch junior query
junior_queryset = Junior.objects.filter(id=junior_id, guardian_code__icontains=str(guardian.guardian_code),
is_invited=True).last()
if junior_queryset:
# use RemoveJuniorSerializer serializer
serializer = RemoveJuniorSerializer(junior_queryset, data=request.data, partial=True)
if serializer.is_valid():
# save serializer
serializer.save()
return custom_response(SUCCESS_CODE['3022'], serializer.data, response_status=status.HTTP_200_OK)
return custom_error_response(serializer.errors, response_status=status.HTTP_400_BAD_REQUEST)
else:
return custom_error_response(ERROR_CODE['2034'], response_status=status.HTTP_400_BAD_REQUEST)
class JuniorTaskListAPIView(viewsets.ModelViewSet):
"""Update guardian profile"""
serializer_class = TaskDetailsSerializer
permission_classes = [IsAuthenticated]
pagination_class = PageNumberPagination
queryset = JuniorTask.objects.all()
def list(self, request, *args, **kwargs):
"""Create guardian profile"""
status_value = self.request.GET.get('status')
search = self.request.GET.get('search')
if search and str(status_value) == '0':
queryset = JuniorTask.objects.filter(junior__auth=request.user,
task_name__icontains=search).order_by('due_date', 'created_at')
elif search and str(status_value) != '0':
queryset = JuniorTask.objects.filter(junior__auth=request.user, task_name__icontains=search,
task_status=status_value).order_by('due_date', 'created_at')
if search is None and str(status_value) == '0':
queryset = JuniorTask.objects.filter(junior__auth=request.user).order_by('due_date', 'created_at')
elif search is None and str(status_value) != '0':
queryset = JuniorTask.objects.filter(junior__auth=request.user,
task_status=status_value).order_by('due_date','created_at')
paginator = self.pagination_class()
# use Pagination
paginated_queryset = paginator.paginate_queryset(queryset, request)
# use TaskDetailsSerializer serializer
serializer = TaskDetailsSerializer(paginated_queryset, many=True)
return custom_response(None, serializer.data, response_status=status.HTTP_200_OK)
class CompleteJuniorTaskAPIView(views.APIView):
"""Update junior task API"""
serializer_class = CompleteTaskSerializer
model = JuniorTask
permission_classes = [IsAuthenticated]
def put(self, request, format=None):
try:
task_id = self.request.data.get('task_id')
image = request.data['image']
if image and image.size == NUMBER['zero']:
return custom_error_response(ERROR_CODE['2035'], response_status=status.HTTP_400_BAD_REQUEST)
filename = f"images/{image.name}"
image_url = upload_image_to_alibaba(image, filename)
# fetch junior query
task_queryset = JuniorTask.objects.filter(id=task_id, junior__auth__email=self.request.user).last()
if task_queryset:
# use RemoveJuniorSerializer serializer
serializer = CompleteTaskSerializer(task_queryset, data={'image': image_url}, partial=True)
if serializer.is_valid():
# save serializer
serializer.save()
return custom_response(SUCCESS_CODE['3032'], serializer.data, response_status=status.HTTP_200_OK)
return custom_error_response(serializer.errors, response_status=status.HTTP_400_BAD_REQUEST)
else:
return custom_error_response(ERROR_CODE['2044'], response_status=status.HTTP_400_BAD_REQUEST)
except Exception as e:
return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST)
class JuniorPointsListAPIView(viewsets.ModelViewSet):
"""Junior Points viewset"""
serializer_class = JuniorPointsSerializer
permission_classes = [IsAuthenticated]
def get_queryset(self):
"""get queryset"""
return JuniorTask.objects.filter(junior__auth__email=self.request.user).last()
def list(self, request, *args, **kwargs):
"""profile view"""
queryset = self.get_queryset()
token = self.request.META['HTTP_AUTHORIZATION']
headers = {
'Authorization': token
}
requests.get(os.getenv('BASE_URL') + '/api/v1/top-junior/', headers=headers)
serializer = JuniorPointsSerializer(queryset)
return custom_response(None, serializer.data, response_status=status.HTTP_200_OK)
-32
View File
@@ -1,32 +0,0 @@
#!/usr/bin/env python
"""Django's command-line utility for administrative tasks."""
"""Django import"""
# Import OS module
import os
# Import sys module"""
import sys
# define all function
# execute command line
# Import execute from command line
# fetch django settings
def main():
"""Main function"""
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'zod_bank.settings')
try:
"""Import execute from command line function"""
from django.core.management import execute_from_command_line
except ImportError as exc:
"""Show Exception error"""
raise ImportError(
"Couldn't import Django. Are you sure it's installed and "
"available on your PYTHONPATH environment variable? Did you "
"forget to activate a virtual environment?"
) from exc
"""execute command line function"""
execute_from_command_line(sys.argv)
if __name__ == '__main__':
main()
+19
View File
@@ -0,0 +1,19 @@
{
"$schema": "https://json.schemastore.org/nest-cli",
"collection": "@nestjs/schematics",
"sourceRoot": "src",
"compilerOptions": {
"deleteOutDir": true,
"watchAssets": true,
"assets": [
{
"include": "config",
"exclude": "**/*.md"
},
{ "include": "common/modules/**/templates/**/*", "watchAssets": true },
{ "include": "common/modules/neoleap/zod-certs" },
"i18n",
"files"
]
}
}
-24
View File
@@ -1,24 +0,0 @@
upstream web {
ip_hash;
server web:8000;
}
# portal
server {
location / {
proxy_pass http://web/;
proxy_set_header Host $http_host;
}
listen 8000;
client_max_body_size 512M;
server_name localhost;
proxy_read_timeout 900;
proxy_connect_timeout 900;
proxy_send_timeout 900;
#proxy_set_header Host $http_host;
location /static {
autoindex on;
alias /usr/src/app/static/;
}
}
View File
-12
View File
@@ -1,12 +0,0 @@
"""
notification admin file
"""
from django.contrib import admin
from notifications.models import Notification
@admin.register(Notification)
class NotificationAdmin(admin.ModelAdmin):
"""Notification Admin"""
list_display = ['id', 'notification_type', 'notification_to', 'data', 'is_read']
-13
View File
@@ -1,13 +0,0 @@
"""
notification app file
"""
# django imports
from django.apps import AppConfig
class NotificationsConfig(AppConfig):
"""
notification app config
"""
default_auto_field = 'django.db.models.BigAutoField'
name = 'notifications'
-16
View File
@@ -1,16 +0,0 @@
"""
notification constants file
"""
REGISTRATION = 1
TEST_NOTIFICATION = 99
NOTIFICATION_DICT = {
REGISTRATION: {
"title": "Successfully registered!",
"body": "You have registered successfully. Now login and complete your profile."
},
TEST_NOTIFICATION: {
"title": "Test Notification",
"body": "This notification is for testing purpose"
}
}

Some files were not shown because too many files have changed in this diff Show More