Systems Hub
শুরুর গল্প

আমার ১০০ জন শিক্ষক আছে
সবাইকে কি একই সেবা দেবো?

শিক্ষক ১ একটু বড় কোর্স করেন — তাকে বেশি Storage দেওয়া উচিত।
শিক্ষক ২ ছোট কোর্স — তাকে কম দাম দিতে চাই। কিভাবে করবো?

👨‍🏫
শিক্ষক রহিম
Live Class + ৫০ GB Storage
মাসে ৫,০০০ টাকা
👩‍🏫
শিক্ষিকা সাবিনা
Live Class + ১০ GB Storage
মাসে ২,৫০০ টাকা
👨‍💼
শিক্ষক করিম
শুধু PDF Upload
মাসে ৫০০ টাকা
❓ সমস্যা কি?
প্রতিটা শিক্ষকের জন্য আলাদা কোড লিখলে → কোড হবে জগাখিচুড়ি, maintain করা অসম্ভব।
দরকার একটা flexible service management system — এটাই AddOn Service!
Common Mistakes

সবাই যে ৫টা ভুল করে

১
Hardcoded Pricing
Teacher.price = 5000 সরাসরি কোডে লিখে রাখা। দাম বদলাতে হলে deploy করতে হয়! 😱
২
একটাই Plan সবার জন্য
"সবাইকে একই সেবা দেবো" — এটা scaling এর সাথে কাজ করে না। ১০০ শিক্ষক মানে ১০০ রকম চাহিদা।
৩
No Offering Flow
সরাসরি assign করা হয়, teacher এর consent নেওয়া হয় না। পরে billing নিয়ে ঝামেলা হয়।
৪
Single Price Type
শুধু MONTHLY রাখে। কিন্তু কেউ PER_CLASS চায়, কেউ PER_STUDENT — flexibility নেই।
৫
No Default Fallback
Custom offer না থাকলে কি হবে? কোনো fallback নেই → system crash বা wrong price দেখায়।
Core Concept

AddOn Service আসলে কি?

ভাবুন এটাকে একটা Menu Card হিসেবে।
SuperAdmin menu তৈরি করে। Teacher অর্ডার করে।

📋
Service Catalog
Live Class, Storage, AI Tools, Bandwidth — এগুলো হলো AddOnService। একবার বানাও, বারবার ব্যবহার করো।
🎯
Targeted Offering
শিক্ষক X-এর জন্য নির্দিষ্ট service নির্দিষ্ট price-এ offer করো। অন্যরা এটা দেখতেই পাবে না।
✅
Teacher Accepts
Teacher offer দেখে নিজে price tier বেছে নেয় → ACCEPTED হয়। Mutual agreement!
🍽️ Real-world analogy: একটা Restaurant এর menu আছে (AddOnService)। Customer (Teacher) order করে (Offering)। Waiter confirm করে (Selection)। Bill আসে selected price অনুযায়ী।
Architecture

৪টা Table — কিভাবে সংযুক্ত?

TABLE 1
AddOnService
id, name, description, code, isActive
e.g. "Live Class", "Storage 50GB"
→
TABLE 2
CourseDefaultService
courseId + serviceId
Course-এর সব teacher-এর জন্য default
↓
TABLE 3
CourseAdminServiceOffering
adminId + courseId + serviceId
status: OFFERED / ACCEPTED
নির্দিষ্ট teacher-এর custom offer
→
TABLE 4
ServiceOfferingPrice
MONTHLY / PER_CLASS / PER_STUDENT
amount, minQty, maxQty
একটা offer-এ অনেক price tier
↓
TABLE 5
ServiceOfferingSelection
offeringId + selectedPriceId + selectedAt
Teacher যে price tier বেছে নিলো
Complete Flow

শুরু থেকে শেষ — Step by Step

১
SuperAdmin: Service বানাও
POST /add-on-services → name="Live Class", code=101
Unique code দিয়ে identify করা যায়
SuperAdmin Only
২
SuperAdmin: Default service set করো
CourseDefaultService → courseId + serviceId
এই course-এর সব teacher automatically এই service পাবে
Auto Apply
৩
SuperAdmin: Custom offer করো
CourseAdminServiceOffering → adminId + courseId + serviceId
Status: OFFERED (pending teacher response)
Custom Deal
৪
SuperAdmin: Price tier যোগ করো
একটা offer-এ multiple prices → MONTHLY ৫০০০, PER_CLASS ২০০
Teacher বেছে নেবে কোনটা চায়
Flexible
৫
Teacher: Accept করো, Price বেছে নাও
Selection তৈরি হয় → status ACCEPTED → decidedAt recorded
Transaction-এ atomically হয়
✓ Done!
Smart Design

Default + Custom — দুটো Layer

🏠
Default Service
✅ Course-এর সব teacher automatically পায়
✅ SuperAdmin একবার set করে, সবাই পায়
✅ isDefault price — standard flat rate
✅ Teacher কোনো action নিতে হয় না
📌 Example: "Basic Live Class Access" → সব teacher-এই আছে
💎
Custom Offering
🎯 নির্দিষ্ট teacher-এর জন্য
🎯 Custom negotiated price
🎯 Multiple price tier দেওয়া যায়
🎯 Teacher নিজে accept করে
📌 Example: রহিম ভাই-এর জন্য "50GB Storage" special rate
🔀 Smart Merge Logic
// Default service আছে, কিন্তু custom offer নেই? const defaultOnlyOfferings = allDefaultServices ?.filter(d => !offerings.find(o => o.courseAdmin.courseId === d.courseId && o.serviceId === d.serviceId // custom নেই, তাহলে default দেখাও )) ?.map(d => ({ id: null, status: 'DEFAULT', ...d })) // merged offerings = custom + default fallback
Pricing System

১৫+ Price Type — Ultimate Flexibility

এটাই সিস্টেমের সবচেয়ে শক্তিশালী অংশ — যেকোনো business model support করে।

Time Based
● MONTHLY — মাসে মাসে
● YEARLY — বার্ষিক
● PER_HOUR / PER_MINUTE
Usage Based
● PER_CLASS — ক্লাস প্রতি
● PER_STUDENT — ছাত্র প্রতি
● PER_API_CALL
● PER_USER
Storage / Data
● PER_MB / PER_GB / PER_TB
● BANDWIDTH
● PER_COURSE
● ONE_TIME / FIXED
💡 minQty / maxQty দিয়ে Volume Discount
Pro Feature
একই service-এ ৩টা tier রাখা যায়:
1-50 student → ৳200/student  |  51-200 student → ৳150/student  |  200+ student → ৳100/student
Code Deep Dive

Offering Offer করা — Validation Chain

✅ 3-Step Validation before offer:
const createCourseAdminServiceOfferIntoDb = async (payload) => { const { courseId, adminId, serviceId } = payload // Check 1: Service exist করে? const service = await prisma .addOnService.findFirst({ where: { id: serviceId, isDeleted: false, isActive: true } }) if (!service) throw AppErrors(404, 'Not found') // Check 2: Admin exist করে? // Check 3: Course exist করে? // সব ঠিক থাকলে offer create return prisma.courseAdminServiceOffering .create({ data: { courseId, adminId, serviceId } }) }
🔒 Teacher Accept = Transaction:
const result = await prisma.$transaction( async (tx) => { // Step 1: Selection তৈরি করো const selection = await tx .courseAdminServiceOfferingSelection .create({ data: { offeringId, selectedPriceId } }) // Step 2: Offering status update করো await tx.courseAdminServiceOffering.update({ where: { id: offeringId }, data: { status: 'ACCEPTED', decidedAt: new Date() } }) return selection })
💡 Transaction মানে — দুটো কাজ একসাথে হয়। একটা ব্যর্থ হলে দুটোই rollback। Data inconsistency শূন্য!
Production Grade

Unique Code Auto-Generate — Race Condition Safe

🤔 সমস্যা: একসাথে ২টা service create হলে কি হবে?
দুইজন একই সময়ে last code=101 পড়ে → দুইজনই 102 দিয়ে চেষ্টা করে → Duplicate! System crash!
// Retry loop দিয়ে race condition handle const createAddOneServiceIntoDb = async (payload) => { const { name, description } = payload for (let attempt = 0; attempt < 3; attempt++) { const last = await prisma.addOnService.findFirst({ orderBy: { createdAt: 'desc' }, select: { code: true } }) const nextCode = Number(last?.code ?? 100) + 1 try { return await prisma.addOnService.create({ data: { name, description, code: `${nextCode}` } }) } catch (e) { if (e?.code === 'P2002') continue // Unique constraint → retry! } } }
P2002 Error
Prisma duplicate key error → retry loop
Max 3 Attempts
3 বার চেষ্টার পরেও না হলে graceful fail
DB Unique Index
code field-এ unique constraint → last line of defense
Data Aggregation

formatTeacherServices — Cost Calculator

একটা function যা সব teacher-এর সব service-এর total monthly cost বের করে।

Price Type → Cost Bucket
switch (price.type) { case 'MONTHLY': totalMonthlyCost += amount; break case 'YEARLY': totalYearlyCost += amount; break case 'ONE_TIME': totalOneTimeCost += amount; break case 'PER_CLASS': // qty-based requiresCalculation = true; break }
Normalized Monthly Cost:
// সব কিছু monthly-তে convert করো const totalMonthly = totalMonthlyCost // direct + totalYearlyCost / 12 // yearly ÷ 12 + totalOneTimeCost / 12 // one-time ÷ 12
সব ধরনের price-কে apples-to-apples compare করা যায়!
📊 Output Summary (per teacher):
5
Total Services
3
Default Services
2
Custom Offerings
৳8,500
Monthly Cost
2
Selected
BDT
Currency
Teacher Experience

Teacher কি দেখে — Dashboard View

আমার Services Dashboard
3 Active
1 Pending
ServicePriceTypeStatus
Live Class Access ৳2,500/মাস DEFAULT ● Active
50GB Storage ৳500/মাস CUSTOM ● Accepted
AI Tools Suite ৳200/ক্লাস CUSTOM ⏳ Offered
Bandwidth Pack ৳1,000 (fixed) DEFAULT ● Active
⏳ "AI Tools Suite" — Offered অবস্থায়:
Teacher দুটো option পাচ্ছে:
Option A: ৳200/class (PER_CLASS)
Option B: ৳3,000/month (MONTHLY)
Teacher decide করবে কোনটা তার জন্য সুবিধাজনক।
✅ Accepted হলে:
• selectedPriceId record হয়
• decidedAt timestamp save হয়
• status = ACCEPTED
• Billing system এই data দিয়ে invoice generate করে
Scalability

কিভাবে Scale করে?

১০০ teacher থেকে ১০,০০০ teacher — same system, no rewrite।

🗂
Pagination সব জায়গায়
buildQueryOptions → skip/take/orderBy। ১০,০০০ record-ও smooth। limit max 1000 enforce করা আছে।
⚡
2-Query Pattern
আগে teacherIds বের করো, তারপর সব offerings একসাথে fetch। N+1 query problem নেই।
🗃
In-memory grouping
offeringsByTeacherAndCourse Map তৈরি করো, DB-তে join না করে। Fast O(n) lookup।
🌐
Hostname-based filtering
একই codebase দিয়ে multiple tenant। buildHostNameCondition() দিয়ে auto-filter।
📈 N+1 Problem বনাম এই System
❌ Wrong way:
teachers.forEach(t => DB.query(t.id)) → 100 DB calls!

✅ Right way:
1. Get teacherIds → 1 query
2. Get all offerings WHERE adminId IN [...teacherIds] → 1 query
3. Group in memory → 0 queries
Total: 2 queries for 100 teachers!
Soft Delete সব জায়গায়
isDeleted: true — কোনো data সত্যিকার delete হয় না। Audit trail থাকে। কোনো FK violation নেই।
DB Schema

Database Schema — Visual Map

AddOnService
id String @id @default(cuid()) name String description String? code String @unique isActive Boolean @default(true) isDeleted Boolean @default(false)
CourseDefaultService
id String courseId String // FK → Course serviceId String // FK → AddOnService isActive Boolean @default(true) isDeleted Boolean @default(false) // prices → DefaultServicePrice[]
CourseAdminServiceOffering
id String adminId String // FK → Admin (Teacher) courseId String // FK → Course serviceId String // FK → AddOnService status Enum // OFFERED | ACCEPTED expiresAt DateTime? decidedAt DateTime?
OfferingPrice + Selection
// Price: offeringId String type PriceType // MONTHLY|PER_CLASS... amount Decimal minQty, maxQty Int? // Selection: offeringId String @unique selectedPriceId String
Recap

AddOn Service — সব কিছু এক নজরে

📋
Service Catalog
AddOnService = reusable menu items। একবার বানাও, বারবার ব্যবহার করো।
🎯
Targeted Offering
প্রতিটা teacher-কে আলাদা deal অফার করা যায়। তারা accept করে নিজের price বেছে নেয়।
💰
15+ Price Types
MONTHLY, PER_CLASS, PER_GB, PER_STUDENT — যেকোনো business model support।
🏠
Default Fallback
Custom offer না থাকলে CourseDefaultService থেকে auto-serve। কখনো empty হয় না।
🏆 Production-Grade Features
✅ Transaction দিয়ে atomic accept
✅ Race condition safe code generation
✅ N+1 query problem নেই (2 queries)
✅ In-memory grouping for speed
✅ Soft delete everywhere
✅ Hostname-based multi-tenant
✅ Pagination + max limit enforced
✅ isRunning / expiresAt check
🎬 এই System দিয়ে কি করা যায়?
১ লাখ teacher × ১০ service × ৫ price tier
= ৫০ লাখ combinations
সব একই codebase দিয়ে!
1 / 14