0%
balqalam Logo
balqalam
FeaturesCommunityPricingDocumentation
Login
  • Introduction
  • Pitch
  • Hogwarts
  • Live Demo
  • MVP
  • Roadmap
  • Launch Sprint
  • PRD
  • Get Started
  • Localhost
  • Architecture
  • Structure
  • Pattern
  • Page
  • Layout
  • Content
  • Types
  • Config
  • Actions
  • Queries
  • Authorization
  • Validation
  • Form
  • Table
  • Detail
  • Card
  • Util
  • Hooks
  • List Params
  • Views
  • README.md
  • ISSUE.md
  • Technology Stack
  • Database
  • File
  • CDN Assets
  • Entry Points
  • Dashboard
  • Authentication
  • Credentials
  • OAuth
  • Flow Diagrams
  • Multi-Tenancy
  • Offline
  • Onboarding
  • Onboarding Videos
  • Add Values
  • Admission
  • Application
  • Attendance
  • Compliance
  • Profile
  • Exams
  • Exam Wizard
  • Timetable
  • Classrooms
  • Notifications
  • Conference
  • LMS (Lumos)
Finance
  • Finance
  • Fee Management
  • Invoice
  • Wallet
  • Salary
  • Payroll
  • Timesheet
  • Expenses
  • Budget
  • Receipt
  • Accounts
  • Banking
  • Reports
  • Dashboard
  • Permissions
  • Messages
  • Integration Flow
  • Provision
  • AI Document Processing
  • Document Intelligence
  • Internationalization
  • Translation
  • Translation Guide
  • Icons
  • Docs Factory
  • Inspiration
  • Listings
  • Teachers
  • Students
  • Catalog
  • Library
  • Contributing
  • Code of conduct
  • GitHub Workflow
  • Database Seeds
  • Database Safety
  • Test Accounts
  • Playwright
  • Prettier
  • Block Rebound
Sales & GTM
  • Marketing Brief
  • Sales
  • Go-to-market
  • Marketing
  • Admission — Feature Spotlight
  • Pilot Program
  • Leads
  • Proposal
  • Outreach Templates
  • Case Study
  • Competitors
  • Landing-page teardown
  • Competitor FAQ
  • Business model
  • Shared economy
  • Traction
Fundraising & Ecosystem
  • Get Support
  • Investor Deck
  • Data Room
  • Investors
  • Accelerators
  • Incubators
  • Grants
  • Sponsors
  • Partners
  • Competitions & Hackathons
  • Universities & Training Centers

Library

PreviousNext

School library management — global-first catalog, lazy-provisioned borrowing, community contributions, admin tools, RBAC.

The library uses a global-first architecture — every school sees the platform-wide CatalogBook catalog out of the box. No setup, no onboarding wiring. Schools can hide individual books; borrowing is tracked via lazily-provisioned school-scoped Book records.

Architecture

GLOBAL (no schoolId)
  CatalogBook (PUBLISHED + APPROVED)         ← all schools see by default

OPT-OUT (schoolId-scoped)
  SchoolBookSelection
    isActive: false  →  hidden for this school
    no record        →  visible (default)

LAZY PROVISIONING
  Book (school-scoped, auto-created on first detail page visit)
    copied from CatalogBook, used for borrow / return tracking only

  BorrowRecord (school-scoped)
    userId, bookId, borrowDate, dueDate, status (BORROWED / RETURNED / OVERDUE)
ApproachSetupData for 100 schools × 90 booksSharing
Per-school provisioningAdmin adds9,000 Book recordsNone
Onboarding wiringAuto9,000 Book recordsNone
Global-first (current)Zero90 CatalogBooks (shared)Visible to all

Benefits: zero setup, no duplication, school autonomy (can hide), lazy efficiency, community contributions enrich all schools.

User flows

Student browses + borrows

Student visits /library
  → content.tsx queries CatalogBook directly (global)
    excludes books hidden by school (SchoolBookSelection.isActive = false)
    categorizes into rows: Latest, Featured, Literature, Science
  → click book card → /library/books/[id]
    loads CatalogBook by ID
    finds or creates school-scoped Book (lazy)
    shows cover, description, video, rating, borrow status, "More by Author"
  → click "Borrow Book"
    borrowBook() creates BorrowRecord + decrements availableCopies
  → click "Return Book"
    returnBook() updates status to RETURNED + increments availableCopies

Admin manages library

/library/catalog       → browse global catalog, select / deselect, adjust copies + shelf
/library/admin         → dashboard
/library/admin/books   → manage table (add from catalog, edit details, delete)

Teacher contributes

/library/contribute    → contributeBook() creates CatalogBook (PENDING + DRAFT)
                         sets contributedBy + contributedSchoolId
SaaS /catalog/approvals → approve → APPROVED + PUBLISHED (visible to all)
                          reject → REJECTED + reason

Routes

RoutePurposeAuth
/libraryLibrary home (hero + book rows)All roles
/library/booksAll books with search / filterAll roles
/library/books/[id]Book detail + borrow / returnAll roles
/library/catalogBrowse global catalogADMIN, DEVELOPER
/library/adminAdmin dashboardADMIN, DEVELOPER
/library/admin/booksManage books tableADMIN, DEVELOPER
/library/admin/books/newAdd book from catalogADMIN, DEVELOPER
/library/contributeSubmit book contributionADMIN, TEACHER, DEVELOPER
/library/contributionsMy contributions historyADMIN, TEACHER, DEVELOPER
/library/my-profileBorrow history + statsAll roles

Data flow

Library home query

// Get books hidden by this school
const hiddenSelections = await db.schoolBookSelection.findMany({
  where: { schoolId, isActive: false },
  select: { catalogBookId: true },
})
const hiddenBookIds = new Set(hiddenSelections.map((s) => s.catalogBookId))
 
// Query global catalog, excluding hidden
const catalogBooks = await db.catalogBook.findMany({
  where: {
    status: "PUBLISHED",
    approvalStatus: "APPROVED",
    visibility: { in: ["PUBLIC", "SCHOOL"] },
    ...(hiddenBookIds.size > 0
      ? { id: { notIn: Array.from(hiddenBookIds) } }
      : {}),
  },
  orderBy: [{ rating: "desc" }, { createdAt: "desc" }],
})

Lazy book provisioning

// Load CatalogBook (global)
const catalogBook = await db.catalogBook.findFirst({
  where: { id: bookId, status: "PUBLISHED", approvalStatus: "APPROVED" },
})
 
// Find or create school Book (for borrowing)
let schoolBook = await db.book.findFirst({
  where: { schoolId, catalogBookId: bookId },
})
 
if (!schoolBook) {
  schoolBook = await db.book.create({
    data: {
      schoolId,
      catalogBookId: catalogBook.id,
      title: catalogBook.title,
      author: catalogBook.author,
      // … copies CatalogBook fields
      totalCopies: 3,
      availableCopies: 3,
    },
  })
}

Browsing uses CatalogBook (zero school data). Borrowing uses school Book (created on demand). No bulk provisioning.

Hide mechanism

await db.schoolBookSelection.upsert({
  where: { schoolId_catalogBookId: { schoolId, catalogBookId } },
  create: { schoolId, catalogBookId, isActive: false },
  update: { isActive: false },
})
  • No record → visible (default)
  • isActive: true → explicitly selected (from catalog UI)
  • isActive: false → hidden for this school

RBAC

Authorization via checkLibraryPermission(role, action) in authorization.ts. Cross-school access always denied.

Rolereadcreateupdatedeleteborrowreturnmanage
DEVELOPERYYYYYYY
ADMINYYYYYYY
TEACHERY———YY—
STUDENTY———YY—
GUARDIANY———YY—
STAFFY——————
ACCOUNTANTY——————
USER———————

Server actions

Book CRUD (actions.ts)

ActionFunctionNotes
CreatecreateBook(params)Requires catalogBookId — no standalone creation
UpdateupdateBook(params)Partial updates, verifies school ownership
DeletedeleteBook(params)Blocked when active borrows exist

Borrow / return

ActionFunctionNotes
BorrowborrowBook({ bookId, userId, schoolId })Transaction: create BorrowRecord + decrement copies
ReturnreturnBook({ borrowRecordId, schoolId })Transaction: update status + increment copies
OverduemarkOverdueBooks()Batch: BORROWED past due date → OVERDUE

Catalog selection (catalog/actions.ts)

ActionFunctionNotes
SelectselectCatalogBook(catalogBookId, copies)Creates bridge + Book in transaction
DeselectdeselectCatalogBook(catalogBookId)Removes bridge, nullifies Book.catalogBookId
ToggletoggleBookSelection(selectionId)Flips isActive (show / hide)
UpdateupdateBookSelection(selectionId, data)Adjust copies, shelf location

Validation schemas

SchemaKey fieldsConstraints
bookSchematitle, author, genre, rating, coverUrl, coverColorrating 0-5, hex coverColor, totalCopies ≥ 1
borrowBookSchemabookId, userId, schoolId, dueDatedueDate must be future
returnBookSchemaborrowRecordId, schoolIdBoth required
updateBookSchemabookId + partial book fieldsAt least one field required
deleteBookSchemabookId, schoolIdBoth required

Types

interface BookListItem {
  id: string
  title: string
  author: string
  genre: string
  coverUrl: string
  coverColor: string | null
  rating: number
  createdAt: Date
}
 
interface Book extends BookListItem {
  description: string
  totalCopies: number
  availableCopies: number
  summary: string
  gradeLevel: string
  schoolId: string
  // optional: isbn, publisher, publicationYear, language, pageCount, videoUrl
}

BookListItem works for both CatalogBook query results and Book records — used by BookList and BookCard.

Configuration

ConstantValuePurpose
MAX_BORROW_DAYS14Default loan period
MAX_BOOKS_PER_USER5Borrow limit per student
BOOKS_PER_PAGE20Pagination size
DEFAULT_COVER_COLOR#000000Fallback for missing covers

Tests

pnpm vitest run src/components/library/ --reporter=verbose                   # 160 tests
pnpm vitest run src/components/catalog/__tests__/setup.test.ts --reporter=verbose   # 54 tests
SuiteTestsCoverage
__tests__/actions.test.ts39All 6 server actions (create, update, delete, borrow, return, overdue)
__tests__/authorization.test.ts358 roles × 7 actions + cross-school + edge cases
__tests__/validation.test.ts46All 5 Zod schemas
catalog/__tests__/actions.test.ts334 catalog selection actions
contribute/__tests__/actions.test.ts7Contribution submission

Data layer (CatalogBook)

Books extend the universal catalog with CatalogBook. Like subjects, books live globally (no schoolId); schools reference them via SchoolBookSelection and a school-scoped Book provisioned lazily for borrow tracking.

CatalogBook carries the canonical metadata (title, author, genre, ISBN, cover, digital file, rating, usageCount) plus the contribution audit trail (contributedBy, contributedSchoolId, approvalStatus, approvedBy, rejectionReason). SchoolBookSelection carries per-school overrides (totalCopies, availableCopies, shelfLocation, customName, isActive). Book.catalogBookId is nullable so deselecting preserves BorrowRecord history.

Contribution flow (specific to CatalogBook)

Teacher / Admin → /library/contribute → contributeBook()
  - auth + tenant context
  - creates CatalogBook (PENDING + DRAFT)
  - sets contributedBy + contributedSchoolId
SaaS → /catalog/approvals
  - approveContent("CatalogBook", id) → APPROVED
  - rejectContent("CatalogBook", id)  → REJECTED + reason
→ visible to all schools (PUBLIC visibility) or just the originating school (SCHOOL visibility)

The contribution form lives at /library/contribute; the contributor's history is at /library/contributions for both ADMIN and TEACHER roles.

SaaS dashboard CRUD

Route /catalog/books. DEVELOPER only — global records, no schoolId. createCatalogBook, updateCatalogBook, deleteCatalogBook plus a detail view at /catalog/books/[bookId] showing the full metadata, cover, digital file info, contribution details, and the list of selecting schools.

Approval integration

CatalogBook is one of five content types in the unified approval system — alongside CatalogQuestion, CatalogMaterial, CatalogAssignment, and LessonVideo. All five share approveContent() and rejectContent() in approval-actions.ts, which switches on contentType. They use the same enums (ApprovalStatus, ContentVisibility, ContentStatus) and the same SaaS review surface at /catalog/approvals.

Seed

pnpm db:seed:single books

90 entries across 8 genres (Arabic Literature, Islamic Studies, Sciences, Mathematics, Quran Sciences, History & Geography, Children's, English, Computer Science & Reference) — all APPROVED + PUBLISHED + PUBLIC, with picsum.photos placeholder covers. Idempotent.

See also

  • Catalog — universal catalog architecture
  • Multi-tenancy — tenant scoping
CatalogContributing

On This Page

ArchitectureUser flowsStudent browses + borrowsAdmin manages libraryTeacher contributesRoutesData flowLibrary home queryLazy book provisioningHide mechanismRBACServer actionsBook CRUD (actions.ts)Borrow / returnCatalog selection (catalog/actions.ts)Validation schemasTypesConfigurationTestsData layer (CatalogBook)Contribution flow (specific to CatalogBook)SaaS dashboard CRUDApproval integrationSeedSee also

Built by Databayt ·

Welcome to balqalam.

A great journey is about to begin.