Run NSS operations with reliable approvals, attendance, and reporting.
NSS Nexus is a multi-tenant operations platform for colleges running NSS programs across yearly batches. It helps program officers, unit leaders, and volunteers manage activities, registrations, attendance, feedback, notifications, and hours with strong role-based access, offline support, and auditable workflows. The product matters because it replaces fragmented manual coordination with a reliable system that preserves official records and scales across colleges.
Program Officer Priya, 38 - Priya manages NSS across one college and multiple batches. She needs dependable approval workflows, permission control, and audit trails so that official records stay accurate and defensible.
Unit Leader Arun, 24 - Arun coordinates volunteers for a specific batch and handles attendance on event day. He needs fast mobile workflows that still prevent duplicate or invalid records.
Volunteer Meera, 19 - Meera participates in activities across campus and wants a simple app for discovery, registration, attendance, feedback, and seeing her NSS hours.
Priya used to manage NSS activities through chat messages, spreadsheet approvals, and manual attendance sheets. When a volunteer missed an event or a leader forgot to update hours, it took hours to reconcile records and explain discrepancies to college staff.
With NSS Nexus, Priya creates an activity, submits it for approval, and the system records the decision, notification intent, and audit trail in one transaction. On event day, Arun opens an attendance session, volunteers scan a QR, and the system writes immutable attendance records while updating official hours automatically.
By the end of the month, Priya has a clean dashboard of approvals, attendance, and hours. Volunteers trust the app because their records appear quickly and consistently, and the college gains a reliable operational system instead of a patchwork of manual follow-ups.
Team & resourcing - Small team - 2 backend engineers, 1 Flutter engineer, 1 product designer, part-time QA, part-time PM
Paste this into Cursor, Bolt, Lovable, or v0 to start building.
Build a mobile-first NSS operations platform called NSS Nexus. Tech stack: Frontend: Flutter, Riverpod, Dio, Freezed, JSON Serializable, Isar, Flutter Secure Storage, Firebase Messaging, Crashlytics, Performance, AutoRoute. Backend: Node.js, Express, MongoDB, Mongoose, Zod, JWT, bcrypt, Pino, Firebase Admin SDK. Architecture: modular monolith backend, transactional outbox, cron-based dispatcher in V1, no microservices, no Kafka, no RabbitMQ. Core product: A multi-tenant NSS SaaS for colleges with colleges, yearly batches, memberships, roles, permissions, activities, registrations, attendance sessions, attendance attempts, attendance, feedback, forms, notifications, dashboards, leaderboard, announcements, admin tools, audit logs, security logs, offline read cache, and queued offline writes. Primary flows to implement: 1. Login, refresh, logout, logout-all, session revocation, membership switching. 2. Activity lifecycle: draft, submit, approve, reject, resubmit, archive. 3. Volunteer registration with capacity checks and idempotency. 4. Attendance sessions with QR, Bluetooth, and manual bulk marking. 5. Feedback submission for attendees only. 6. Notification inbox, read state, preferences, and FCM token management. 7. Admin screens for announcements, outbox retries, system config, and security events. 8. Offline mode with Isar cached reads and pending write queue. Data model to create: Users, Sessions, Colleges, Batches, Memberships, Projects, Activities, Registrations, ActivitySessions, AttendanceAttempts, Attendance, Feedback, Forms, FormResponses, Notifications, NotificationOutbox, AuditLogs, SecurityEventLogs. Use publicId fields for user-facing resources and Mongo ObjectIds internally. Make Attendance, Feedback, FormResponses, AuditLogs, SecurityEventLogs, and NotificationOutbox immutable or append-only as appropriate. Backend requirements: Implement RequestContext with actor, tenant, resource, and flags. Enforce server-derived tenant scope and permission-based authorization. Use MongoDB transactions for critical writes. Implement a transactional outbox with cron sweep and retry/dead-letter handling. Add error codes, global error handler, request IDs, health and readiness endpoints, config service, and maintenance mode. Flutter requirements: Create a clean feature-based architecture with feature/data, feature/domain, feature/presentation, and feature.dart exports. Use Riverpod providers, typed failures, permission-driven UI, shared contracts, local caching in Isar, and offline pending actions with idempotency keys. Screens to scaffold: Splash, Login, Membership Picker, Dashboard, Activity List, Activity Detail, Create Activity, Approval Queue, Attendance Session, QR Scanner, Manual Attendance, Feedback, Notifications, Notification Preferences, Forms, Members, Permissions, Announcements, Settings, Admin Outbox, Security Events. Implementation notes: Keep V1 simple and production-ready. Do not build microservices, distributed locks, a dedicated search engine, payment infrastructure, or generic repository/job frameworks. Prioritize clean state machines, testability, tenant isolation, and reliable notifications.
NSS SaaS Platform — Architecture Specification v13.0 Final This is the final updated architecture plan based on the original v12.4 specification and the architectural review. It preserves the useful parts of the original design while simplifying the infrastructure and correcting the reliability assumptions. The original already had the right foundation: multi-tenant college/batch structure, membership-based RBAC, explicit state transitions, transactions, shared JS/Dart contracts, and layered Flutter architecture. --- 1. Platform Frontend: Flutter mobile application Backend: Node.js + Express Database: MongoDB + Mongoose Authentication: JWT access tokens + hashed refresh tokens Notifications: Firebase Cloud Messaging Cache: Redis-compatible cache Optional in V1 Primary background mechanism: MongoDB Outbox + scheduled cron sweeps Future background mechanism: BullMQ + worker when VPS/managed worker infrastructure exists Observability: Pino backend logging Sentry backend Firebase Crashlytics + Performance on Flutter Deployment: Hostinger Node.js Web App / compatible Node hosting initially VPS later without domain/business-logic changes The application must remain fully functional without a permanently running worker. That is the important hosting constraint, rather than tying the architecture permanently to a particular Hostinger product. --- 2. Core Objective The platform digitizes NSS operations across multiple colleges with: Multi-tenant colleges Yearly batches Membership-based RBAC Dynamic permissions Projects Activities Activity approval workflow Volunteer registration Attendance NSS hours Feedback Dynamic forms Notifications Leaderboards Dashboards Persistent multi-device authentication Audit logging Security event logging Tenant suspension Membership suspension Membership exit Remote announcements Offline read cache Offline write queue Idempotent critical writes Transactional persistence Reliable notification intent Future monetization Future exports/certificates The original specification correctly centered the system around Colleges → Batches → Memberships → Users. --- 3. Core Architectural Principles 3.1 Modular monolith The backend is a modular monolith, not microservices. One Node application │ ├── Auth ├── Colleges ├── Batches ├── Memberships ├── Projects ├── Activities ├── Registration ├── Attendance ├── Feedback ├── Forms ├── Notifications ├── Dashboard ├── Leaderboard ├── Admin └── System No Kafka. No RabbitMQ. No microservices. No distributed architecture until scale genuinely requires it. --- 4. Overall Backend Architecture HTTP Request │ ▼ Global Middleware │ ▼ Authentication │ ▼ Session Guard │ ▼ Tenant / Membership Context │ ▼ Authorization │ ▼ Validation │ ▼ Scope / Resource Loading │ ▼ Controller │ ▼ Domain Service │ ├──────────────► Business Rules │ ├──────────────► Repository / Mongoose │ └──────────────► Domain Events │ ▼ best-effort reactions Critical side effects: Domain Service │ ▼ MongoDB Transaction ├── Domain mutation ├── Notification └── Outbox record │ ▼ COMMIT │ ▼ Cron / Worker Dispatcher │ ▼ External service The critical change from v12.4 is that EventEmitter2 is no longer responsible for guaranteeing critical database side effects. --- 5. Domain Layers Backend follows: Routes ↓ Middleware ↓ Controllers ↓ Domain Services ↓ Business Rules ↓ Repositories / Mongoose ↓ MongoDB Controllers Controllers only: read request validate already-validated input call one domain service return response No business rules. No database orchestration. No direct notifications. --- 6. Business Rules Rules are pure logic. ActivityRules RegistrationRules AttendanceRules FeedbackRules MembershipRules NotificationRules PermissionRules FormRules TenantRules Rules must: contain no database access contain no HTTP logic contain no Firebase calls contain no side effects The original document's separation of Rules and Services is retained. --- 7. Domain Services Core services: AuthService SessionService CollegeService BatchService MembershipService PermissionService ProjectService ActivityLifecycleService RegistrationService AttendanceService AttendanceSessionService FeedbackService FormService FormResponseService NotificationService NotificationDispatchService OutboxService DashboardService LeaderboardService SearchService SystemConfigService AnnouncementService AuditService SecurityEventService Every domain service receives: Service(ctx, payload) but ctx is now split conceptually into actor and tenant information. --- 8. Request Context — Updated Instead of assuming every request has a normal membership, use: RequestContext request requestId traceId ip userAgent timestamp actor userId sessionId membershipId? permissions tenant collegeId? batchId? resource optional loaded resource flags membershipCompleted This is important because SUPER_ADMIN is not always operating through a normal college/batch membership. --- 9. Tenant Model There are three scopes. GLOBAL Users Sessions PermissionRegistry SystemConfig System-level Roles Migrations Seeds TENANT College College-specific Roles Announcements TENANT + BATCH Membership Project Activity Registration Attendance Feedback Forms FormResponses Notifications Every service explicitly knows which scope it operates in. --- 10. Multi-Tenancy Rules Normal users: collegeId = ctx.tenant.collegeId batchId = ctx.tenant.batchId Every tenant-owned query must enforce those scopes. Never trust: collegeId batchId supplied from the Flutter client. The backend derives the authoritative values from the authenticated context. SUPER_ADMIN can operate globally through explicitly authorized administrative paths. --- 11. Shared Contract Package Retain: packages/shared/ js/ errorCodes.js permissionKeys.js eventNames.js featureFlags.js notificationTypes.js auditEvents.js securityEvents.js appConfigKeys.js enums.js dart/ error_codes.dart permission_keys.dart event_names.dart feature_flags.dart notification_types.dart audit_events.dart security_events.dart app_config_keys.dart enums.dart shared_contracts.dart The original shared-contract design is retained. CI verifies that exported keys remain synchronized. Do not over-engineer this into a complicated monorepo build system immediately. --- 12. IDs MongoDB ID Use native MongoDB ObjectId internally. Public ID Use public IDs for user-facing display. Example: USR_xxxxxxxxxx ACT_xxxxxxxxxx MBR_xxxxxxxxxx COL_xxxxxxxxxx Public IDs: are display-safe are stable are not MongoDB lookup keys internally API URL decision Prefer public-facing resource IDs in new public routes where practical: /api/v1/activities/:publicId The service resolves the public ID to the internal ObjectId. If using ObjectId URLs during initial implementation is materially simpler, that is acceptable for V1; the internal database design must not depend on the URL representation. --- 13. Global Schema Convention User-facing/domain collections should contain: publicId createdBy updatedBy createdAt updatedAt Infrastructure collections do not need pointless public IDs. Examples that can remain internal: _migrations _seeds Immutable collections: Attendance Feedback FormResponses AuditLogs SecurityEventLogs NotificationOutbox No update/delete API on immutable records. --- 14. Roles System roles: SUPER_ADMIN PROGRAM_OFFICER UNIT_LEADER PROJECT_LEADER VOLUNTEER New roles can be seeded/created without rewriting the authorization architecture. --- 15. Permission System Permission format: resource.action.scope Examples: activity.create.own activity.create.any activity.read.own activity.read.any activity.update.own activity.update.any activity.approve member.read.any member.manage member.suspend member.exit attendance.mark.manual attendance.mark.qr attendance.mark.bluetooth attendance.read notification.send.batch feedback.submit feedback.read.any form.create form.read.any form.respond form.responses.read report.read report.export No business code should depend on: role === "PROGRAM_OFFICER" unless the operation is genuinely a system-level role exception. The original permission hierarchy remains valid. --- 16. Permission Resolution Effective permissions: (role.permissions ∪ granted) − revoked Membership contains: permissionOverrides granted[] revoked[] permissionsVersion Cache key: membership:{membershipId}:{permissionsVersion} Redis is the preferred cache. An in-process cache may exist as a performance fallback. Correctness must never depend on cache invalidation. Changing permissions increments: permissionsVersion so old cache keys naturally become obsolete. --- 17. Permission Management Rules: SUPER_ADMIN → can modify everyone PROGRAM_OFFICER → UL and below UNIT_LEADER → volunteers only An actor can never grant a permission they themselves don't possess. Cannot modify SUPER_ADMIN permissions through normal permission management. Every permission change: transaction + permissionsVersion increment + audit event + domain event --- 18. Collections Users Users _id publicId name passwordHash accountStatus ACTIVE SUSPENDED accountSuspendedAt accountSuspendedBy accountSuspendReason personal dob sex bloodGroup caste contact phone email address notificationPreferences activityCreated activityReminder approvalUpdates generalAnnouncements createdAt updatedAt Users are permanent identities. Account suspension blocks login across all colleges/batches. --- 19. Sessions Sessions _id publicId userId refreshTokenHash deviceInfo platform model appVersion ip userAgent lastUsedAt revoked revokedAt revokedReason createdAt updatedAt Indexes: unique refreshTokenHash (userId, revoked) (userId, lastUsedAt) Refresh tokens are hashed. --- 20. Colleges Colleges _id publicId name uniqueCode status ACTIVE SUSPENDED ARCHIVED suspendedAt suspendedBy suspendReason createdAt updatedAt Behavior: ACTIVE → normal SUSPENDED → reads allowed → writes blocked except authorized SUPER_ADMIN operations ARCHIVED → operationally inaccessible → data retained --- 21. Batches Batches _id publicId collegeId year active config totalRequiredHours categoryTargets ABP1 ABP2 COLLEGE UNIVERSITY notificationConfig activityCreated activityReminder feedbackConfig enabled requireRating requireComment approvalConfig autoApproveActivities Unique: (collegeId, year) --- 22. Memberships — Core Model Memberships _id publicId userId collegeId batchId roleId roleName nssYear academic class division rollNo yearOfJoining enrollmentId assignedProjectIds[] permissionOverrides granted[] revoked[] status ACTIVE SUSPENDED EXITED COMPLETED suspension suspendedAt suspendedBy suspendReason exit exitedAt exitedBy exitReason exitType hoursAtExit joinedAt version permissionsVersion Indexes: unique(userId, batchId) unique(collegeId, batchId, enrollmentId) only when enrollmentId is present/non-empty (batchId, roleName) (batchId, status) --- 23. Membership State Machine ACTIVE ├──→ SUSPENDED ├──→ EXITED └──→ COMPLETED SUSPENDED ├──→ ACTIVE ├──→ EXITED └──→ COMPLETED EXITED └──→ ACTIVE only SUPER_ADMIN COMPLETED └── terminal/read-only PO cannot: exit PO exit UL suspend PO suspend UL operate outside own batch Past official records remain intact. --- 24. Projects Projects _id publicId collegeId batchId name description active Index: (batchId, active) --- 25. Activities Activities _id publicId title description startDateTime endDateTime location projectId category ABP1 ABP2 COLLEGE UNIVERSITY hours maxParticipants registrationDeadline collegeId batchId version lifecycle status DRAFT PENDING APPROVED REJECTED approvalFlowEnabled approvedBy approvedAt rejectionReason resubmittedFrom revisionCount isArchived archivedAt notificationOverride enabled customMessage reminderMessage feedbackEnabled --- 26. Activity Hours Rule Important NSS rule: hours ≠ duration hours is the officially credited NSS value. It is manually assigned. It is not automatically derived from: endDateTime - startDateTime The original specification explicitly separated these concepts. --- 27. Activity State Machine DRAFT ↓ submit PENDING ├── approve → APPROVED └── reject → REJECTED REJECTED ↓ resubmit DRAFT Optional alternate resubmission: REJECTED ↓ new Activity old Activity → ARCHIVED All transitions happen through: ActivityLifecycleService No controller can directly mutate status. --- 28. Activity Visibility DRAFT creator only PENDING creator + PO + authorized UL/PL APPROVED creator + administrative users + volunteers REJECTED relevant management users Volunteer database query must enforce: status = APPROVED isArchived = false The original visibility model is retained. --- 29. Activity Revision V1: revisionCount resubmittedFrom isArchived Do not build a separate revision collection yet. Later, if detailed revision history becomes necessary: ActivityRevision can be introduced without changing the main activity API. --- 30. Registrations Registrations _id publicId membershipId activityId registeredAt idempotencyKey collegeId batchId Constraints: unique(membershipId, activityId) unique(idempotencyKey) Registration rules: activity approved activity not archived deadline not passed membership ACTIVE capacity available Capacity must be checked atomically. --- 31. Attendance — New Structured Design Instead of treating QR/Bluetooth as merely an Attendance.method, introduce: ActivitySession AttendanceAttempt Attendance This makes attendance much easier to expand later. --- 32. ActivitySession ActivitySessions _id publicId activityId status NOT_STARTED OPEN CLOSED attendanceMode QR BLUETOOTH MANUAL attendanceOpensAt attendanceClosesAt attendanceTokenVersion createdAt updatedAt The entity represents the operational attendance window for the activity. --- 33. AttendanceAttempt Short-lived operational record: AttendanceAttempts _id activitySessionId membershipId method QR BLUETOOTH MANUAL attemptedAt deviceId / session context result ACCEPTED REJECTED DUPLICATE EXPIRED OUTSIDE_WINDOW This can have a short retention period. It is useful for security and troubleshooting. --- 34. Attendance Official immutable record: Attendance _id publicId membershipId activityId activitySessionId status PRESENT ABSENT method QR BLUETOOTH MANUAL markedBy timestamp idempotencyKey collegeId batchId createdAt Unique: (membershipId, activityId, activitySessionId) Attendance remains permanent official data. --- 35. QR Attendance QR should represent a temporary attendance session/token. Example conceptual flow: Leader opens attendance ↓ ActivitySession OPEN ↓ server creates short-lived token ↓ leader displays QR ↓ volunteer scans ↓ server validates token/session ↓ membership eligibility check ↓ Attendance created Never trust the QR alone. The server remains authoritative. --- 36. Bluetooth Attendance Bluetooth is a transport/discovery mechanism, not the attendance authority. Flow: ActivitySession ↓ temporary session identifier ↓ Bluetooth proximity/discovery ↓ Volunteer identifies session ↓ server validation ↓ Attendance record Do not encode trusted attendance information entirely inside the Bluetooth payload. --- 37. Manual Attendance Authorized leader: select activity session ↓ load registered/eligible members ↓ mark present/absent ↓ bulk submission ↓ transaction Bulk attendance must be transactional. --- 38. Feedback Feedback _id publicId activityId membershipId rating comment submittedAt idempotencyKey collegeId batchId Rules: attendance PRESENT membership ACTIVE one feedback per activity Feedback is immutable. --- 39. Dynamic Forms Forms _id publicId title description fields[] label type required options target ROLE BATCH PROJECT linkedTo GENERAL ACTIVITY active allowMultipleSubmissions collegeId batchId Initial field types: text number dropdown checkbox date textarea The original six-field form design is retained. --- 40. Form Responses FormResponses _id publicId formId membershipId responses submittedAt collegeId batchId If: allowMultipleSubmissions = false use a unique constraint: (formId, membershipId) If multiple submissions are allowed, do not enforce that uniqueness. This fixes the ambiguity in the original unconditional-unique-index design. --- 41. Notifications Notifications _id publicId title message type APPROVAL ACTIVITY ATTENDANCE SYSTEM BROADCAST scope BATCH COLLEGE GLOBAL templateKey preferenceKey target type value data requestId collegeId batchId createdAt Instead of an ever-growing readBy[] array for potentially large audiences, prefer a recipient/read model when notification volume grows. V1 can still use readBy[] for small-scale usage if query patterns remain acceptable. --- 42. Notification Delivery Model This is one of the most important changes. The guarantee is: > Exactly-once creation of the logical notification/outbox intent, with at-least-once dispatch attempts and client deduplication. Not exactly-once FCM delivery. --- 43. Transactional Outbox When a business action needs a notification: MongoDB transaction 1. update domain object 2. create Notification 3. create NotificationOutbox COMMIT Example: Approve Activity │ ├── Activity.status = APPROVED ├── Notification = created └── Outbox = created All three succeed or fail together. --- 44. NotificationOutbox NotificationOutbox _id publicId notificationId status PENDING PROCESSING SENT FAILED DEAD attempts nextAttemptAt lastAttemptAt claimedAt lastError sourceEventId requestId createdAt updatedAt Indexes: (status, nextAttemptAt) unique sparse(sourceEventId) sourceEventId prevents duplicate logical outbox creation. --- 45. Outbox Dispatcher V1 Cron ↓ find eligible outbox rows ↓ atomic claim ↓ FCM ↓ mark result No permanent worker required. Later Outbox ↓ BullMQ ↓ Worker ↓ FCM The business transaction remains unchanged. --- 46. Remove FCM From the HTTP Critical Path Do NOT: approve request ↓ wait for FCM ↓ return HTTP response Instead: approve request ↓ Mongo transaction ↓ commit ↓ HTTP success FCM is delivered asynchronously afterwards. This keeps the core API independent of FCM latency. --- 47. Retry Strategy Example: attempt 1 → immediate attempt 2 → 30 sec attempt 3 → 2 min attempt 4 → 10 min attempt 5 → 30 min After configured maximum attempts: DEAD Admin can inspect and retry dead-letter records. --- 48. Notification Deduplication Every logical notification has: notificationId Flutter stores/handles the notification ID and avoids displaying duplicate logical notifications. Thus: server duplicate dispatch does not become: duplicate user-visible notification --- 49. Domain Events Events remain useful, but they are no longer the reliability mechanism for critical persistence. Catalog: activity.created.v1 activity.submitted.v1 activity.approved.v1 activity.rejected.v1 activity.resubmitted.v1 activity.archived.v1 registration.created.v1 attendance.marked.v1 feedback.submitted.v1 membership.roleChanged.v1 membership.permissionChanged.v1 membership.suspended.v1 membership.unsuspended.v1 membership.exited.v1 membership.exitReversed.v1 user.accountSuspended.v1 user.accountUnsuspended.v1 form.responseSubmitted.v1 session.created.v1 session.revoked.v1 systemConfig.changed.v1 Each event contains: eventName eventId occurredAt requestId collegeId? batchId? actorUserId actorMembershipId? data --- 50. Event Bus EventEmitter2 may be retained as a lightweight in-process event bus. Use it for: metrics best-effort logging non-critical cache refresh local application reactions Do not depend on it for durable writes. Do not require an EventEmitter listener to make the core transaction correct. --- 51. Audit Events Audit log is separate from security events. Examples: ACTIVITY_CREATED ACTIVITY_APPROVED ACTIVITY_REJECTED ATTENDANCE_MARKED ATTENDANCE_BULK_MARKED MEMBERSHIP_SUSPENDED MEMBERSHIP_EXITED MEMBERSHIP_EXIT_REVERSED PERMISSION_CHANGED SESSION_REVOKED COLLEGE_SUSPENDED ANNOUNCEMENT_CREATED The original audit catalog is retained conceptually. --- 52. Audit Log AuditLogs _id publicId action performedByUserId performedByMembershipId roleAtAction collegeId batchId entity entityId details requestId ip userAgent createdAt Immutable. No update/delete route. Important historical actor information is stored independently of future membership changes. --- 53. Security Event Log SecurityEventLogs _id eventType userId? ip userAgent requestId details createdAt Examples: FAILED_LOGIN SESSION_REVOKED INVALID_TOKEN PERMISSION_ESCALATION_ATTEMPT RATE_LIMIT_EXCEEDED TENANT_SUSPENDED_WRITE_ATTEMPT MAINTENANCE_MODE_BYPASS_ATTEMPT SUSPENDED_ACCOUNT_LOGIN_ATTEMPT SUSPENDED_MEMBERSHIP_ACCESS_ATTEMPT EXITED_MEMBERSHIP_ACCESS_ATTEMPT CRON_SECRET_MISMATCH --- 54. Authentication Access JWT: short-lived Refresh token: long-lived hashed in database Session: one record per login/device Flow: Login ↓ create Session ↓ issue access JWT ↓ issue refresh token Refresh: verify refresh token ↓ load session ↓ ensure not revoked ↓ issue new access token Support: logout current device logout all devices admin force revoke password change → revoke sessions account suspension → revoke all --- 55. Refresh Token Rotation Prefer refresh-token rotation. old refresh token ↓ validated ↓ revoke/rotate old token ↓ new refresh token Store only hashes. This improves session security without changing the overall architecture. --- 56. Middleware Pipeline Global: requestId logger helmet cors compression request timeout JSON body limit mongo sanitize hpp Per route: 1. authenticate 2. sessionGuard 3. build RequestContext 4. maintenanceGuard 5. tenantSuspensionGuard 6. authorize(permission) 7. validate(Zod) 8. tenant/resource scope validation 9. resource loading 10. visibility/ownership checks 11. lifecycle validation 12. idempotency validation 13. optimistic locking 14. rate limiting 15. feature gate 16. controller 17. domain service The original pipeline is retained conceptually, but durable side effects stay in the service/database transaction rather than middleware. --- 57. Idempotency Critical writes: registration attendance feedback selected administrative actions must accept: Idempotency-Key Pattern: request ↓ idempotency key ↓ existing successful response? yes → return existing result no → execute transaction The database uniqueness constraint remains the final safety mechanism. --- 58. Idempotency Storage Use: short-term cache + database constraint Do not make Redis the sole source of idempotency correctness. If Redis disappears, the DB constraint still prevents duplicate records. --- 59. Optimistic Locking Use version numbers on mutable high-conflict entities: Activity.version Membership.version Flow: client reads version 4 server update where version = 4 success → version 5 someone else already changed: version != 4 → 409 CONCURRENT_MODIFICATION Don't add optimistic locking to every collection just because it is available. --- 60. State Machines Centralize transitions. Activity: VALID_TRANSITIONS Membership: VALID_MEMBERSHIP_TRANSITIONS No arbitrary: model.status = ... outside the relevant domain service. --- 61. Caching Strategy Keep V1 intentionally simple. Backend Primary source: MongoDB Cache only where useful: permissions dashboard leaderboard Redis is an optimization. No correctness may depend on Redis. Flutter Primary local cache: Isar Do not simultaneously build an aggressive Dio cache + Isar cache + separate in-memory cache unless profiling proves the need. --- 62. Search Create: SearchService for genuine search operations: searchActivities searchMembers searchProjects searchNotifications Initial engine: MongoDB Later: Typesense Elasticsearch/OpenSearch can replace the implementation. The original abstraction is retained. --- 63. Pagination Use: Cursor pagination for high-volume feeds: notifications activities members audit logs Use normal page/limit only where a total count is genuinely useful. Avoid calculating expensive total counts on every high-volume endpoint. --- 64. Dashboard Dashboards are derived queries. Do not store dashboard totals as primary data. Examples: Volunteer hours Activity count Attendance Category totals Approval queue Feedback summary Use MongoDB aggregation pipelines. Cache the result temporarily when needed. --- 65. NSS Hours Official hour calculation: Attendance PRESENT ↓ Activity.hours ↓ member total Do not use activity duration. Do not persist total hours as a normal aggregate. Exception: Membership.exit.hoursAtExit This preserves the official point-in-time record when someone exits. The original specification intentionally made hoursAtExit the sole persisted aggregate exception. --- 66. Leaderboard Derived from: Attendance.PRESENT + Activity.hours Sort: hours DESC joinedAt ASC EXITED members: remain visible + Exited badge SUSPENDED members: remain visible according to leaderboard policy Cache briefly. Never treat the cache as source of truth. --- 67. Scheduled Jobs V1 uses hosting cron. Internal services: OutboxSweepService ReminderSweepService RetentionService BackupService One cron entry can trigger: POST /internal/cron/run and execute the required jobs. Or separate cron endpoints can be retained when operational visibility is more useful. --- 68. Reminders On activity approval: Activity approved ↓ calculate reminder time ↓ persist reminder information V1: cron scans approved activities instead of requiring BullMQ delayed jobs. Use: reminderSent or a dedicated reminder record to guarantee one logical reminder. Later: BullMQ delayed job can replace cron scanning. --- 69. FCM Topics Topics: batch_{batchId} role_{roleName}_{batchId} project_{projectId} Topic membership changes on: login membership switch role change project assignment change suspension unsuspension exit account suspension token refresh Rules: SUSPENDED → unsubscribe restricted membership topics UNSUSPENDED → resubscribe ACCOUNT SUSPENDED → unsubscribe all applicable topics Do not use topics as the only source of authorization. The server still decides whether a notification should exist. --- 70. Notification Targeting For important targeted notifications: direct user token For broadcast-like notifications: FCM topic Do not blindly send the same notification to both mechanisms. The notification record must define the authoritative target. --- 71. Notification Preferences Priority: Activity override ↓ Batch configuration ↓ Template default ↓ User preference Preference should always be checked before dispatch unless the notification is explicitly classified as mandatory/system-critical. --- 72. Application Configuration Registry: APP_CONFIG_KEYS MAINTENANCE_MODE MAX_UPLOAD_SIZE_MB FEEDBACK_WINDOW_HOURS ATTENDANCE_QR_EXPIRY_SECONDS MAX_ACTIVITY_HOURS REGISTRATION_CLOSE_BEFORE_MINUTES LEADERBOARD_CACHE_TTL DASHBOARD_CACHE_TTL MAX_FCM_TOKENS_PER_USER All config keys are defined centrally. Never: SystemConfigService.get("randomString") without a registered key. --- 73. Remote Announcements System announcement: title message type active visibleTo startsAt endsAt Returned through: GET /api/v1/app-config Flutter can display without an app release. The original announcement approach is retained. --- 74. Maintenance Mode If enabled: normal users → blocked SUPER_ADMIN → allowed Exceptions: /health /ready /app-config remain accessible. --- 75. Tenant Suspension College: ACTIVE SUSPENDED ARCHIVED Suspended: GET allowed writes blocked SUPER_ADMIN bypass Every blocked write can generate a security event. --- 76. Rate Limits Categories: Login Refresh General Activity management Manual notifications Attendance Reports/export Example policy: Login: strict Refresh: strict General: moderate Attendance: higher Reports: low Manual FCM: low Exact limits should be configurable rather than architectural constants. --- 77. API Error Architecture Base: AppError Hierarchy: ValidationError AuthenticationError AuthorizationError NotFoundError ConflictError BusinessRuleError InfrastructureError Flutter switches on: errorCode never on: message --- 78. API Response Contract Success: { "success": true, "message": "...", "data": {}, "meta": {}, "requestId": "..." } Error: { "success": false, "errorCode": "PERMISSION_DENIED", "message": "...", "details": {}, "requestId": "...", "timestamp": "..." } Version: /api/v1 Backward compatibility: adding fields/endpoints is allowed breaking field removals/renames require a new API version The original error-contract principle is retained. --- 79. Core Error Codes VALIDATION_ERROR PERMISSION_DENIED TENANT_MISMATCH TENANT_SUSPENDED MAINTENANCE_MODE_ACTIVE ACCOUNT_SUSPENDED MEMBERSHIP_SUSPENDED MEMBERSHIP_EXITED MEMBERSHIP_COMPLETED ACTIVITY_FULL REGISTRATION_DEADLINE_PASSED DUPLICATE_SUBMISSION CONCURRENT_MODIFICATION RESOURCE_NOT_FOUND SESSION_REVOKED INVALID_TOKEN INVALID_STATE_TRANSITION ACTIVITY_NOT_APPROVED FEEDBACK_NOT_ELIGIBLE RATE_LIMIT_EXCEEDED EXIT_REASON_REQUIRED CANNOT_EXIT_THIS_ROLE ATTENDANCE_WINDOW_CLOSED ATTENDANCE_TOKEN_EXPIRED ATTENDANCE_ALREADY_MARKED --- 80. Health and Readiness GET /health checks application health. GET /ready reports: Mongo cache FCM configuration cron configuration maintenance state Do not mark the entire application unhealthy merely because optional Redis is unavailable. --- 81. Environment Configuration Required: MONGO_URI JWT_SECRET JWT_REFRESH_SECRET FCM_SERVICE_ACCOUNT CRON_SECRET Optional: REDIS_URL NODE_ENV LOG_LEVEL SENTRY_DSN REQUEST_TIMEOUT_MS DISPATCH_TIMEOUT_MS All environment access is centralized in: ConfigService --- 82. Secrets Never commit: JWT secrets FCM service account database URI cron secret Redis credentials storage credentials Use hosting environment variables/secrets management. --- 83. File Storage Not required for initial V1. When required: StorageService with: S3-compatible storage Cloudflare R2 or equivalent No large file blobs stored directly inside MongoDB. --- 84. Async Exports Don't build the entire generic job system in V1. But design exports as: POST /exports ↓ ExportJob ↓ worker/cron ↓ generate CSV/PDF/XLSX ↓ object storage ↓ notification Large exports should never block an HTTP request. The original async-export direction is retained. --- 85. Data Retention Suggested categories: Sessions → short retention after revocation NotificationOutbox SENT → short retention NotificationOutbox DEAD → longer retention Notifications → defined retention Security events → defined retention Audit logs → longer retention Attendance → indefinite Feedback → indefinite Form responses → indefinite or policy-defined Retention must be policy-driven and configurable. --- 86. Backup Strategy Development: free/development MongoDB tier Pilot: paid database tier Production: database tier with appropriate backup/restore capability Backup: scheduled encrypted off-site versioned Target workflow: MongoDB ↓ backup/export ↓ object storage Most important: periodic restore test A backup that has never been restored should not be considered verified. --- 87. Disaster Recovery Maintain: database backup environment configuration deployment instructions migration files seed files Recovery: restore MongoDB ↓ deploy backend ↓ run/verify migrations ↓ verify health ↓ resume cron --- 88. Database Transactions Use transactions for: registration + capacity activity approval + notification + outbox permission change + permissionsVersion bulk attendance membership exit + hoursAtExit + future registration handling account suspension + session revocation Do not automatically wrap every database read in a transaction. --- 89. Membership Exit On exit: Membership.status = EXITED hoursAtExit = calculated official hours future registrations = cancelled past attendance = retained past feedback = retained past forms = retained The exit itself is transactional. --- 90. Offline Architecture Flutter local storage: Isar Use it for: cached activities membership notifications preferences pending actions --- 91. Offline Reads Read flow: Isar ↓ immediate UI ↓ network refresh ↓ server response ↓ Isar update Show: STALE when cached content is old. --- 92. Offline Writes Do not pretend every write succeeded. Pending action states: PENDING SYNCING SYNCED FAILED_RETRYABLE FAILED_PERMANENT Record: id endpoint method payload idempotencyKey createdAt retryCount lastAttemptAt errorCode serverResponse Idempotency key is generated at the original write, not at retry. The original design already had the correct instinct to persist the idempotency key for offline retries. --- 93. Offline Write Flow User action ↓ generate idempotencyKey ↓ try network │ ├── success → SYNCED │ └── offline → PENDING ↓ reconnect ↓ SYNCING ↓ server response │ ┌──────┴──────┐ ↓ ↓ success failure ↓ ↓ SYNCED retryable/permanent Never silently discard a permanent failure. --- 94. Flutter Architecture Retain: Flutter ├── core ├── shared ├── features ├── gen └── test Feature architecture: feature/ data/ domain/ presentation/ feature.dart This was one of the strongest parts of the original plan. --- 95. Flutter Data Layer data/ datasources/ remote local dtos/ repositories/ implementation Remote datasource: API only Local datasource: Isar only Repository: combines remote/local maps DTO → Entity --- 96. Flutter Domain Layer domain/ entities/ repositories/ usecases/ Entities: pure Dart no JSON annotations Use cases are optional. Do not create: one use case class per trivial method Only introduce a use case where orchestration/logic genuinely benefits from isolation. --- 97. Flutter Presentation presentation/ providers/ screens/ widgets/ State management: Riverpod Prefer: AsyncNotifier Notifier Provider Use generated providers where appropriate. --- 98. Screen Rule Screens are coordinators. Normal screen: loads state routes actions assembles widgets Complex screen: screen ├── header ├── content ├── actions └── sections Do not enforce a rigid 200-line law. Use ~200 lines as a maintainability signal, not a compiler rule. --- 99. Feature Boundary Each feature has: feature.dart which is its public API. Other features should not depend directly on implementation internals. But don't create enormous barrel exports; export only intentionally public classes. --- 100. Core Flutter Structure lib/ main.dart app.dart core/ bootstrap/ di/ network/ storage/ observability/ connectivity/ config/ errors/ extensions/ hooks/ utils/ shared/ design_system/ dashboard_registry/ domain/ widgets/ router/ l10n/ contracts/ features/ auth/ membership/ activities/ attendance/ feedback/ forms/ notifications/ dashboard/ leaderboard/ members/ permissions/ announcements/ settings/ gen/ test/ --- 101. Bootstrap Sequential startup: INITIALIZING 1. RestoreSession 2. RestoreMembership 3. LoadAppConfig 4. LoadEnums 5. Initialize FCM 6. Initialize Crashlytics 7. Load Permissions ↓ READY Other states: LOGIN_REQUIRED MEMBERSHIP_REQUIRED MAINTENANCE SUSPENDED ERROR The ordered bootstrap concept should remain. --- 102. Authentication Bootstrap If JWT exists: validate ↓ expired? ↓ refresh If session is revoked: clear credentials → LOGIN_REQUIRED If account suspended: clear credentials → SUSPENDED --- 103. Membership Switching User may have multiple memberships. Example: College A / Batch 2026 College B / Batch 2027 Only one is active in the current app context. Selecting membership: update active membership ↓ refresh permissions ↓ update FCM topics ↓ update crash context ↓ refresh batch-scoped data --- 104. Permission Provider Everything in the Flutter UI uses: permissionsProvider and: PermissionKeys No widget should ask: roleName == "PO" for ordinary authorization decisions. The server remains the final authority. --- 105. Design System Keep: AppColors AppTypography AppSpacing AppRadius AppShadows Components: AppButton AppCard AppDialog AppTextField AppBadge AppBottomSheet AppSnackbar AppShimmer States: LoadingView ErrorView EmptyView OfflineView The original design-system principle remains strong. --- 106. Dashboard Registry DashboardWidgetDefinition id requiredPermission? priority builder Widgets: hoursProgress categoryChart approvalQueue feedbackInsights formResponses leaderboardPreview memberStatus Dashboard automatically assembles according to available permissions. --- 107. Networking Use Dio. Interceptor sequence: AuthInterceptor RefreshInterceptor IdempotencyInterceptor RetryInterceptor Remove the need for a heavy Dio cache interceptor if Isar is the authoritative client cache layer. --- 108. Request Headers Useful headers: Authorization X-Request-Id X-Membership-Id Idempotency-Key X-Membership-Id identifies the active membership context. The backend verifies that the membership belongs to the authenticated user. --- 109. Retry Interceptor Retry: network failures timeouts selected transient server failures Do not retry blindly: 400 401 403 422 Critical writes only retry when idempotency makes retry safe. --- 110. Local Database Isar models: LocalActivity LocalMembership LocalNotification LocalPreferences LocalPendingAction Potential later models: LocalProject LocalForm LocalLeaderboard Only cache what actually benefits from offline access. --- 111. FCM Flutter Integration FCM handles: foreground notification background notification notification tap token refresh Topic manager handles: membership topic role topic project topic When membership changes: unsubscribe old subscribe new invalidate permissions refresh batch-scoped providers --- 112. Crash Monitoring Flutter: Firebase Crashlytics Firebase Performance Backend: Sentry Pino Avoid duplicate error platforms unless a specific use case justifies them. Every backend error should carry: requestId to correlate with Flutter logs. --- 113. Navigation AutoRoute or equivalent declarative router. Guards: AuthGuard MembershipGuard PermissionGuard MaintenanceGuard Navigation should respond to permissions, not hardcoded role names. --- 114. Full UI Screen Set Authentication Splash Login Membership Picker Session Management Dashboard Volunteer Dashboard PO/UL Dashboard Activity Activity List Activity Detail Create Activity Draft Management Approval Flow Attendance QR Scanner Manual Attendance Attendance Session Feedback Feedback Screen Forms Dynamic Form Form Management Notifications Notification List Notification Preferences Management Member Management Permission Management Reports/system Leaderboard Announcement Management User Account Suspension Settings The original specification's overall 23-screen scope is retained. --- 115. Attendance UX QR: Open scanner ↓ scan ↓ validate ↓ confirm Manual: member list ↓ mark ↓ bulk submit Bluetooth: discover activity session ↓ select ↓ server validation ↓ confirm attendance --- 116. Metadata Endpoint Add: GET /api/v1/metadata or keep metadata inside /app-config. Return: activity categories roles statuses notification types attendance methods membership statuses Flutter can use these for presentation rather than hardcoding every backend enum. Shared contracts still remain authoritative for programmatic enums used by both sides. --- 117. App Config Endpoint GET /api/v1/app-config Response: features appConfig announcement enums minimumSupportedAppVersion? recommendedAppVersion? The version fields can be added later. --- 118. API Endpoints — Core POST /api/v1/auth/login POST /api/v1/auth/refresh POST /api/v1/auth/logout POST /api/v1/auth/logout-all Membership: GET /api/v1/me GET /api/v1/me/memberships GET /api/v1/me/permissions POST /api/v1/me/membership-switch --- 119. Activity Endpoints GET /api/v1/activities POST /api/v1/activities GET /api/v1/activities/:id PATCH /api/v1/activities/:id POST /api/v1/activities/:id/submit POST /api/v1/activities/:id/approve POST /api/v1/activities/:id/reject POST /api/v1/activities/:id/resubmit POST /api/v1/activities/:id/archive Registration: POST /api/v1/activities/:id/register DELETE /api/v1/activities/:id/register --- 120. Attendance Endpoints POST /api/v1/activities/:id/attendance/session POST /api/v1/attendance/qr/verify POST /api/v1/attendance/bluetooth/verify POST /api/v1/activities/:id/attendance/manual POST /api/v1/activities/:id/attendance/bulk GET /api/v1/activities/:id/attendance All writes are server-authoritative. --- 121. Feedback Endpoints POST /api/v1/activities/:id/feedback GET /api/v1/activities/:id/feedback --- 122. Forms Endpoints GET POST PATCH /api/v1/forms GET POST /api/v1/forms/:id/responses --- 123. Member Management GET /api/v1/memberships PATCH /api/v1/memberships/:id/suspend PATCH /api/v1/memberships/:id/unsuspend POST /api/v1/memberships/:id/exit PATCH /api/v1/memberships/:id/exit/reverse Account suspension: PATCH /api/v1/users/:id/account/suspend PATCH /api/v1/users/:id/account/unsuspend --- 124. Notifications GET /api/v1/notifications PATCH /api/v1/notifications/:id/read PATCH /api/v1/notifications/read-all GET /api/v1/notifications/preferences PATCH /api/v1/notifications/preferences FCM token: PUT /api/v1/me/fcm-token DELETE /api/v1/me/fcm-token --- 125. Admin GET/POST/PATCH /api/v1/admin/announcements GET /api/v1/admin/outbox GET /api/v1/admin/security-events GET/PATCH /api/v1/admin/system-config GET /api/v1/admin/users Outbox: POST /api/v1/admin/outbox/:id/retry --- 126. Internal Cron Protected by: X-Cron-Secret Endpoints: POST /internal/cron/run or separate endpoints: /internal/cron/outbox /internal/cron/reminders /internal/cron/retention /internal/cron/backup The single-runner model is preferred for simpler hosting. --- 127. Repository Layer Unlike the earlier plan, do not implement a gigantic generic repository abstraction. Use repositories where they help isolate persistence: ActivityRepository MembershipRepository NotificationRepository OutboxRepository but simple services may use focused Mongoose model operations directly when repository indirection provides no value. This keeps the codebase pragmatic. --- 128. Caching Rule Never write: if cache missing → business behavior changes Correct: if cache available → faster if cache unavailable → MongoDB Cache is never authoritative. --- 129. No Distributed Locks in V1 Avoid: Redlock distributed locking Use MongoDB atomic claims. Example: findOneAndUpdate( { status: PENDING, nextAttemptAt <= now }, { status: PROCESSING, claimedAt: now } ) Only one dispatcher claims a row. --- 130. Exactly-Once vs At-Least-Once — Final Semantics The architecture promises: Database transaction: exactly-once logical state transition Notification creation: exactly-once logical notification intent Outbox: unique logical work item Dispatch: at-least-once attempts FCM: delivery is external and not treated as exactly-once Client: notificationId deduplication This is the correct reliability model. --- 131. Security Baseline Use: helmet CORS policy input size limits Zod Mongo injection sanitization HPP JWT validation refresh-token hashing rate limiting secure secrets audit logging security event logging Passwords: bcrypt or another strong password hashing implementation. Never log: password JWT refresh token FCM service-account credentials --- 132. Tenant Isolation Test Requirement Every tenant-owned endpoint must have tests for: same college/same batch → allowed same college/different batch → blocked different college → blocked SUPER_ADMIN → explicitly allowed where appropriate This should be treated as a security invariant, not just a normal unit test. --- 133. Audit vs Security Audit: who performed a valid business action Security: who attempted something suspicious/invalid Never merge them into one generic log. --- 134. Soft Delete / Retention User: never delete Membership: state machine Activity: archive Project: active=false Form: active=false College: suspend/archive Session: revoke → later delete Notification: retain → retention policy Attendance: immutable Feedback: immutable Audit: immutable Security: immutable --- 135. Monetization Keep future-ready support: BatchSubscription plan features adsEnabled status But V1: all features enabled no payment dependency Feature gates should exist only where they are actually needed. Don't build payment infrastructure yet. --- 136. Feature Registry Initial features: FORMS FEEDBACK LEADERBOARD REPORTS CERTIFICATES AI_INSIGHTS CUSTOM_BRANDING V1 can have these enabled. Later subscription logic can turn features on/off. --- 137. Search Expansion Later When MongoDB search becomes inadequate: SearchService ↓ Typesense/OpenSearch/Elasticsearch Only the implementation changes. The application does not directly depend on the external search provider. --- 138. Exports Later Phase 2: ExportJob Types: CSV Excel PDF attendance report volunteer report hours report activity report Large jobs: generate asynchronously upload notify --- 139. Certificates Later Potential future architecture: CertificateTemplate CertificateJob GeneratedCertificate Storage object Do not make the V1 Activity schema responsible for certificate generation. --- 140. Future Features Deferred: geo-fenced attendance attendance fraud scoring waitlist conditional form fields email notifications email digest certificates bulk import bulk export webhooks university portal AI insights white-label branding advanced metrics The original deferred feature list can remain largely unchanged. --- 141. Testing Architecture Layer 1 — Unit Test: all Rules state machines permission resolution error hierarchy event validation hour calculations attendance validation registration rules Aim for very high coverage on business rules. --- 142. Layer 2 — Integration Use a real Mongo test environment/replica-set-compatible test setup. Test: transactions unique constraints outbox idempotency permission versions membership suspension membership exit tenant isolation attendance activity transitions The original integration-testing emphasis should remain. --- 143. Layer 3 — API Test: authentication authorization tenant isolation error codes pagination validation idempotency version conflicts Especially: ACCOUNT_SUSPENDED MEMBERSHIP_SUSPENDED MEMBERSHIP_EXITED TENANT_MISMATCH CONCURRENT_MODIFICATION INVALID_STATE_TRANSITION --- 144. Layer 4 — E2E Critical journeys: College creation Batch creation User enrollment Membership assignment Login Membership selection Activity creation Activity submission Activity approval Volunteer registration QR attendance Manual attendance Feedback Leaderboard Membership suspension Membership exit Account suspension Notification delivery Offline write synchronization --- 145. Security E2E Scenarios Must specifically test: user from College A attempting College B activity user from Batch A attempting Batch B member suspended account attempting refresh revoked session attempting API request suspended membership attempting attendance exited membership attempting feedback permission escalation attempt forged tenant IDs forged membership ID duplicate idempotency key --- 146. Migration System Keep simple custom migration runner: _migrations filename checksum appliedAt Migrations must be: idempotent ordered checksum tracked Never edit an already-applied migration. Create a new migration. --- 147. Seed System _seeds Initial: permission registry roles super admin notification templates feature registry system config app config keys Seeds must be idempotent. --- 148. ADRs Retain ADRs but update them. Recommended: ADR-001 Membership RBAC ADR-002 Transactional Outbox ADR-003 Public IDs ADR-004 Cron-first dispatch ADR-005 In-process events are non-critical ADR-006 Hosting strategy ADR-007 MongoDB deployment tiers ADR-008 Version-keyed permission cache ADR-009 RequestContext ADR-010 Activity state machine ADR-011 Shared contract package ADR-012 Async exports ADR-013 Search abstraction ADR-014 Suspension model ADR-015 hoursAtExit ADR-016 Flutter feature architecture ADR-017 Feature public boundaries ADR-018 Screen decomposition ADR-019 Offline write queue ADR-020 Attendance session architecture ADR-021 Notification delivery semantics ADR-022 Cache strategy The original ADR-driven approach is worth keeping. --- 149. Build Order — Final Phase 0 — Architecture & Contracts Shared contracts Environment configuration Error codes Permission keys Enums Event schemas Notification types Audit/security catalogs ADR documents --- 150. Phase 1 — Backend Foundation Express ConfigService MongoDB Mongoose MigrationRunner SeedRunner Pino Request IDs Error hierarchy Global error handler Users Sessions Roles Permissions Memberships Auth Login Refresh Logout Session management RequestContext Tenant resolution Permission resolution Health Readiness App config Maintenance mode Tenant suspension Do not start notifications yet. --- 151. Phase 2 — Core NSS Domain Colleges Batches Projects Activities ActivityRules ActivityLifecycleService State machine Activity API Activity queries Activity approval Activity rejection Resubmission Archive At the end of Phase 2, you should be able to perform the basic NSS activity lifecycle without notifications. --- 152. Phase 3 — Transactional Outbox + Notifications Notification model NotificationOutbox model NotificationService OutboxService FCM integration Cron dispatcher Retry Dead-letter handling Notification preferences Notification templates This is now independent of the HTTP request lifecycle. --- 153. Phase 4 — Registration & Attendance Registration Capacity transactions ActivitySession AttendanceAttempt Attendance QR attendance Manual attendance Bluetooth attendance only after QR/manual are stable This order reduces complexity. --- 154. Phase 5 — Feedback & Forms Feedback Feedback eligibility Dynamic forms Form responses Validation Multiple submission behavior --- 155. Phase 6 — Member Management Suspend Unsuspend Exit Reverse exit Account suspension Permissions management FCM topic lifecycle --- 156. Phase 7 — Flutter Foundation Flutter project shared contracts design system Dio secure storage Isar Riverpod routing bootstrap error mapping connectivity FCM Crashlytics --- 157. Phase 8 — Flutter Features Order: Auth Membership Dashboard Activities Registration Attendance Feedback Notifications Forms Leaderboard Members Permissions Announcements Settings Admin --- 158. Phase 9 — Offline Only after online flows are stable: Isar read cache stale indicators connectivity state pending write queue sync engine retry permanent failure UI Don't attempt full offline-first behavior before server flows work correctly. --- 159. Phase 10 — Dashboard & Reporting aggregation pipelines volunteer dashboard PO dashboard UL dashboard leaderboard report endpoints --- 160. Phase 11 — Reliability & Operations cron runner outbox sweeps reminder sweeps retention backup restore testing Sentry rate limits security events admin tools --- 161. Phase 12 — Production Hardening Before real college data: tenant-isolation audit permission audit load testing backup restore test FCM failure testing offline sync testing concurrency testing rate-limit testing security testing session revocation testing --- 162. Phase 13 — Infrastructure Upgrade Only when usage requires it: Managed Redis/Valkey BullMQ Dedicated worker Better MongoDB tier Object storage PM2/Docker Advanced monitoring Application/domain code should not change. Only infrastructure adapters change. --- 163. Later VPS Architecture Flutter ↓ Node API ↓ MongoDB Redis │ ├── cache └── BullMQ ↓ Worker ↓ Outbox ↓ FCM This is the final target architecture without requiring microservices. --- 164. Final Backend Folder Structure backend/ src/ app.js config/ config.service.js env.schema.js middleware/ request-id.js authenticate.js session-guard.js membership-context.js maintenance-guard.js tenant-suspension-guard.js authorize.js validate.js scope-guard.js ownership-guard.js lifecycle-guard.js idempotency.js optimistic-lock.js rate-limit.js feature-gate.js controllers/ routes/ services/ auth/ session/ college/ batch/ membership/ permission/ project/ activity/ registration/ attendance/ feedback/ forms/ notifications/ dashboard/ leaderboard/ search/ system/ rules/ activity.rules.js registration.rules.js attendance.rules.js feedback.rules.js membership.rules.js permission.rules.js notification.rules.js form.rules.js models/ repositories/ only where abstraction is useful events/ event-bus.js catalog.js schemas/ listeners/ outbox/ outbox.service.js outbox.dispatcher.js outbox.sweeper.js notifications/ templates.js fcm.service.js cron/ cron-runner.js outbox-job.js reminder-job.js retention-job.js backup-job.js security/ security-event.service.js audit/ audit.service.js search/ search.service.js errors/ utils/ health/ docs/ adr/ migrations/ seeds/ tests/ --- 165. Final Flutter Folder Structure lib/ main.dart app.dart core/ bootstrap/ di/ network/ storage/ connectivity/ observability/ config/ errors/ extensions/ hooks/ utils/ shared/ design_system/ tokens/ components/ states/ dashboard_registry/ domain/ membership_context.dart permission_checker.dart widgets/ router/ l10n/ contracts/ features/ auth/ membership/ activities/ attendance/ feedback/ forms/ notifications/ dashboard/ leaderboard/ members/ permissions/ announcements/ settings/ gen/ test/ The original three-layer structure remains the foundation. --- 166. Flutter Feature Template Every substantial feature: feature/ data/ datasources/ feature_remote_datasource.dart feature_local_datasource.dart dtos/ feature_dto.dart repositories/ feature_repository_impl.dart domain/ entities/ feature.dart repositories/ feature_repository.dart usecases/ optional presentation/ providers/ feature_provider.dart screens/ feature_screen.dart widgets/ feature.dart --- 167. Flutter Shared State Cross-feature state: membershipContext permissionChecker appConfig connectivity announcement Features should not directly import another feature's internal repository or datasource. --- 168. Flutter Dependency Injection Central: core/di/providers.dart Test overrides: core/di/overrides.dart Providers should not construct their own deep dependencies inline. --- 169. Flutter Error Handling API: HTTP error ↓ Failure(errorCode) ↓ UI mapping Never: if (message == "You are suspended") Use: MEMBERSHIP_SUSPENDED instead. --- 170. Flutter Cache Authority For normal read data: Isar = local cache Server = authority For critical writes: Server = authority Isar = pending/local state This is the final offline consistency model. --- 171. Final Guarantees Backend guarantees Multi-tenant isolation Membership-based authorization Centralized business rules Explicit state machines Transactional critical operations Idempotent critical writes Durable notification intent Outbox retry At-least-once dispatch attempts Client notification deduplication Audit/security separation Server-authoritative permissions Server-authoritative attendance No critical reliance on Redis No critical reliance on EventEmitter2 No critical reliance on FCM availability No worker required for V1 Hosting migration without domain rewrite --- 172. Flutter Guarantees Layered architecture Feature isolation Riverpod state management Shared contract constants Server-authoritative writes Isar read caching Offline write queue Idempotent retries Typed error handling Permission-driven UI Ordered bootstrap Reusable design system Crash reporting Performance tracing --- 173. What Is Explicitly NOT in V1 Do not build: Microservices Kafka RabbitMQ GraphQL Kubernetes Generic repository framework Generic job framework Dedicated search engine Distributed locks Permanent worker requirement Full event-sourcing Complex CQRS UUIDv7 database migration AI analytics Geo-fencing Webhooks Certificate engine White-label engine Payment system The architecture should remain deliberately boring until actual scale demands complexity. --- 174. Final Technology Decision Backend Node.js Express Mongoose MongoDB Zod JWT bcrypt Pino Firebase Admin SDK Redis-compatible cache — optional Background MongoDB Transactional Outbox + Cron Later: BullMQ + Worker Flutter Flutter Riverpod Dio Freezed JSON Serializable Isar Flutter Secure Storage Firebase Messaging Firebase Crashlytics Firebase Performance AutoRoute or equivalent Reactive Forms or equivalent Storage later S3-compatible object storage --- 175. Final Architecture Diagram ┌─────────────────────────┐ │ Flutter │ │ │ │ Riverpod │ │ Isar │ │ Secure Storage │ │ FCM │ │ Crashlytics │ └────────────┬────────────┘ │ HTTPS │ ┌────────────▼────────────┐ │ Express API │ │ │ │ Auth │ │ Tenant Context │ │ RBAC │ │ Validation │ │ Controllers │ └────────────┬────────────┘ │ Domain Services │ ┌────────────▼────────────┐ │ Business Rules │ │ │ │ Activity │ │ Membership │ │ Attendance │ │ Registration │ │ Feedback │ └────────────┬────────────┘ │ MongoDB Transaction │ ┌─────────────────┼─────────────────┐ │ │ │ ▼ ▼ ▼ Domain Notification Outbox Data Data Row │ │ │ └─────────────────┴─────────────────┘ │ COMMIT │ ▼ Cron / Dispatcher │ ┌────────────┴────────────┐ │ │ ▼ ▼ FCM Future BullMQ Worker --- 176. Final Verdict This is the architecture I would actually build. The original v12.4 foundation was already strong, especially around memberships, RBAC, state machines, shared contracts, testing, and Flutter separation. The important changes in v13.0 are: v12.4 EventEmitter2 → critical listener → notification ↓ FCM inline becomes: v13.0 Domain Service ↓ Mongo Transaction ├── domain state ├── notification └── outbox ↓ COMMIT ↓ Cron/Worker ↓ FCM And: "exactly-once FCM" is replaced with: exactly-once logical intent + at-least-once dispatch + notificationId deduplication The other major simplification is: V1: MongoDB + Outbox + Cron V2: MongoDB + Redis + Outbox + BullMQ + Worker rather than building infrastructure in advance. That gives you a production-capable modular monolith, strong tenant isolation, reliable business transactions, a proper NSS attendance model, offline capability, and a clean migration path to VPS infrastructure without prematurely turning the project into an enterprise distributed system.
Design by The Resonance | Powered by GPC – The AI Transformation Company