Skip to content
Browse all help topicsSkip to article

Architecture at a glance

NTPU AI Learn splits the system into two paths. To read public content, the browser simply fetches pages that were prerendered at build time. Anything that needs trust, such as submitting an assessment, syncing progress, signing in or using instructor and admin features, goes to the Worker API, which validates and authorizes the request before it reads or writes the database.

The read path needs no sign-in, makes no API call and never touches the database, so public content is fast and indexable. The interactive path is the only way to read or write learning data, and every request on it is validated and authorized inside the Worker. Answer keys and instructor-only content live only on the server and never appear on the public path.

Some interactive actions need more than a valid, authorized request. Before something like a course-completion certificate or a capability certificate is granted, the Worker also runs server-side eligibility rules that check real evidence (such as completed lessons and passed assessments, or an instructor's reviewed and passed judgment of submitted work) before anything is issued. Nothing is ever granted just because a form was submitted.

System Context

This diagram focuses on one thing: who uses NTPU AI Learn, and how the platform interacts with the outside world.

It lets someone approaching the platform for the first time quickly understand the system boundary and the overall picture.

Who uses NTPU AI Learn
  • Learner

    Reads courses, completes learning and assessments

    • Reads courses, completes learning and assessments leads to NTPU AI Learn
  • Instructor

    Teaches, and views sessions and learning results

    • Teaches, views sessions and learning results leads to NTPU AI Learn
  • Administrator

    Manages the platform, sessions and permissions

    • Manages the platform, sessions and permissions leads to NTPU AI Learn
  • NTPU AI Learn

    AI learning and practice platform

    • Confirms sign-in identity leads to Google sign-in
    • Checks the submitter is human leads to Cloudflare Turnstile
  • Google sign-in

    One of the optional sign-in methods

  • Cloudflare Turnstile

    Bot verification on public forms, enabled per form

Container Architecture

This diagram unpacks the single "NTPU AI Learn" box from System Context into the technical components that are actually deployed and run. It answers: what are the main execution and data components, what does each own, and how do they talk to each other.

Container: which path a request takes
  • Browser

    Learner, instructor or admin

    • Read public content leads to Static Assets
    • Interactive action (/api/) leads to Worker API
  • Static Assets

    Prerendered HTML, JS and CSS

    Zone: Public

  • Worker API

    Validation, identity, authorization, scoring

    Zone: Server only

    • Read and write leads to D1 database
    • Upload and signed download leads to R2 file storage
  • D1 database

    Accounts, sign-in sessions, progress, submissions, results

    Zone: Persistent data

  • R2 file storage

    Credential evidence files and protected teacher slide exports; file metadata kept in D1

    Zone: Persistent data

  • Version-controlled content

    Courses, Help and Docs, with stable IDs shared by both languages

    • Generated at build time leads to Static Assets
  • Server-side protected logic

    Answer keys, speaker notes, role checks

    Zone: Server only

    • Deployed only with the Worker leads to Worker API

How one action moves through the system

Two real examples: one that only reads public content, and one that needs server-side scoring and a database write.

Scenario 1: reading a course module

A learner opens a module page of a course without signing in.

  1. The browser requests the page
    For example, a module URL under /courses/….
  2. The Worker handles the URL
    Legacy URLs are permanently redirected at this point, and every response gets security headers.
  3. A prerendered page is returned
    The complete HTML, CSS and JS generated at build time come from Static Assets. No API call, no database read, no sign-in.
  4. The browser shows it and takes over
    The content is readable immediately. Once JavaScript loads it adds interaction such as the language switch, self-checks and “Mark as complete”; by default progress stays in this browser only.
Scenario 2: completing a post-test

A learner in a course session takes the post-test using a session code and a learner code.

  1. Start the attempt
    The browser sends a POST to an assessment API under /api/ with the session code and learner code (no sign-in needed). The Worker validates the input, confirms the session is open, creates an attempt record in D1 and returns the questions without answers.
  2. Submit the answers
    When the learner finishes, the browser sends another POST with the option chosen for each question.
  3. Schema validation
    The Worker first checks the request against its schema; a malformed request is rejected right away.
  4. Check the attempt and session
    The Worker finds the attempt and confirms the session is still open. An attempt that was already submitted gets its original result back and is never rescored. When bot verification is enabled, it is checked on the server here too.
  5. Score on the server
    After confirming every question is answered and every question exists, the Worker scores the attempt against answers that exist only on the server and summarizes performance by competency.
  6. Write to D1
    The score and each answer are written to the database with parameterized queries.
  7. Return the result and update progress
    The response carries the score and competency breakdown (a post-test also includes the learner’s own pre-test score). The page shows the result and marks the assessment complete in the browser’s progress.

Components

Main components, responsibilities and trust levels
ComponentResponsibilityTechnologyTrust level
Front end (in the browser)Renders pages, handles interaction, keeps learning progress locallyReact, TypeScript, ViteUntrusted: runs on the user’s device, where anyone can inspect and change it
Static AssetsServes prerendered HTML, JS, CSS and practice filesCloudflare Static Assets, with pages prerendered at build timePublic: no sign-in, holds only content that may be public
Worker APIRedirects and 404s, schema validation, identity and role checks, scoring, data reads and writesCloudflare Workers, Hono, ZodTrust boundary: every authorization and scoring decision is made here
D1 databaseStores accounts, sign-in sessions, course sessions, progress, submissions and assessment resultsCloudflare D1, versioned migrationsTrusted persistent data: reachable only through the Worker
R2 file storageTwo private buckets: credential evidence files (each file's metadata is kept separately in D1) and pre-built teacher slide exportsCloudflare R2Trusted persistent data: reachable only through the Worker; private and assessor-only evidence is downloaded with time-limited signed URLs, and teacher exports are streamed only to authorized teachers, with each attempt audited
Email deliverySends transactional email such as account verification, password reset and credential policy-change noticesCloudflare Workers' built-in send-email capabilityOnly the Worker can trigger it; the content is decided on the server, never by the front end
Structured contentThe text and structure of courses, the Help Center and the docsTypeScript content modules in version controlPublic content enters the site at build time; protected content ships only with the Worker

Design principles

Behind the technology choices above are six principles that the code and the build actually enforce. The table below adds why each principle matters, what it means in practice, and the trade-off that comes with adopting it.

Six design principles
PrincipleWhyConsequenceTrade-off
Public by defaultCourse content, Help and Docs can be read and indexed by search engines without signing in or running JavaScript, which matters most for a first-time visitor who has not registered yet.Public pages must be fully renderable as HTML at build time; content and layout must make sense with no interactive state.Anything designed to be public must be treated as visible to anyone; anything that needs protection has to start out in a protected, server-side-only source, not be hidden by the front end after the fact.
Server-authoritativeAnyone can inspect and change the code running in a browser, so only a place a user cannot tamper with can hold an authoritative decision, such as scoring or authorization.Every operation that needs authorization or scoring is checked again once the Worker receives the request, never trusted from the front end alone.Every protected action costs an extra round trip to the Worker; the front end cannot show a final result from local state alone and has to wait for the server response.
Single source of truthThe same fact defined in two places eventually drifts apart, whether that is a URL, a content ID or the course catalog.Routing, redirects, 404s, prerendered pages and the sitemap all come from one route table; the course catalog and content each have one definition.Changing this shared definition has a wider blast radius and needs more careful verification, not a quick edit in just the one place that uses it.
ReproducibleMaintainers need to be able to rebuild the whole site and the database schema from source, rather than depending on state left behind by a one-off manual change.Content, schemas and database migrations all live in version control, so every change is traceable and can be rebuilt.The database changes only through a new migration; production tables cannot be edited directly, which adds a step even under time pressure.
Progressive enhancementIt keeps public content readable even if JavaScript fails to load or is turned off, which helps both usability and reliability.Public pages need a build-time process that produces complete HTML; interactive features must be layered on top of readable content, not replace it.The build pipeline gets more complex, since it has to maintain both a "readable as delivered" state and a "interactive after load" state; some interactive experiences need a separate implementation path.
Least exposureLeaving one extra piece of data that should not be public in the output is one more leak risk, especially for answer keys and instructor-only content.Answer keys, speaker notes and admin logic live only in protected, server-side-only source and never get bundled into the public front end; every build scans the public output.If the build scan finds a leak, the build fails, and the developer has to fix the leak before deploying, even under a deadline.

Quality Attributes

These are concrete scenarios rather than abstract adjectives, describing how the platform actually behaves in a few quality dimensions. Each one can be checked against the code or a test; none of them is an unmeasured numeric target.

  1. Accessibility
    Even if client-side JavaScript fails to run, public course content remains fully readable, because it is already HTML generated at build time (see "Progressive enhancement").
  2. Security
    Modifying the code running in the browser cannot let a learner change an authoritative authorization decision or an assessment score; those decisions are made only in the Worker, see "Trust boundaries".
  3. Privacy
    Public pages and the front-end bundle never contain protected assessment answers, instructor-only notes, or unnecessary user data; every build scans all public output files and fails if it detects a leak.
  4. Reliability
    When the database is unavailable, public learning content that was already prerendered is still served through Static Assets; only interactive features that need to read or write the database (such as submitting an assessment) are affected, see "Failure and degradation" below.
  5. Maintainability
    Changes to URLs, routing and the database schema are reviewed in version control; one route table produces client routing, redirects, 404s and the sitemap, so production behavior and documentation cannot drift apart.
  6. Reproducibility
    Given the same source revision and a supported toolchain, a maintainer can rebuild the entire public site and reconstruct the database schema by applying the versioned migrations in order.
  7. Internationalization
    Switching language keeps you on the same logical page; because course, docs and assessment content share the same stable IDs across both languages, automated tests check that section structure, links and IDs stay in parity between them.

Content and data

“Content” and “user data” are different things, stored and managed separately. Editing course text never touches learner data, and nothing learners do is written back into the content.

Structured content

  • Courses, the Help Center and these docs
  • Kept in version control, not in a separate content management system
  • Reviewed, then published with each build
  • Stable IDs shared by both languages, with automated structure checks
  • The web version is the only authoritative source; print and slide files are derived

User data

  • Accounts, sign-in sessions, course sessions and learner codes
  • Synced progress, assessment answers and results
  • Submissions such as Use Cases, Canvases and Exit Tickets
  • Stored in D1 and read or written only through the Worker API
  • Its structure changes only through versioned migrations

Learning progress in your browser is a third kind: it stays on your device unless you choose to sync it to a course session.

Course feedback components

Course feedback consists of a browser form, four groups of Worker APIs, D1 tables, schedules and mail delivery, all inside the existing trust boundary: authorization, masking, thresholds and audit happen in the Worker.

Main parts of course feedback
PartWhat it is
Form page/feedback/:course, one page in three sections with a progress bar; drafts stay in the browser; the instructor closing slide and QR code are generated in the browser
Learner API/api/feedback: read and write your own feedback, courses still waiting for feedback, anonymous form events, and the live session-code check (session-check)
Instructor and HR APIs/api/teacher-feedback (statistics per session) and /api/org-feedback (organization statistics, comments, CSV, authorized per organization)
Admin API/api/feedback-admin: comment moderation and form performance; retention uses the existing /api/retention-admin; session course binding uses /api/admin/sessions (with an impact preview)
Projector QR page/teacher/feedback-qr: the instructor picks a session and shows its own QR code, a countdown and an A4 print layout full screen; the link carries ?session= so a scan fills the session in
D1 tablesFeedback, comments (original and masked text), one-time reminder records and anonymous form-event counters; the HR role lives in the organization membership table
SchedulesA monthly retention report (report only) and a daily one-time reminder email (off in production by default)
DecksThe closing page of the last module's instructor deck is produced by the offline export script and published to the teacher-export R2 bucket

Trust boundaries

Three trust zones
ZoneContainsWho can reach itHow it is protected
PublicHTML, JS, CSS, public course content, practice files, Help and DocsAnyone, no sign-inHolds only publishable content; every build scans it for leaks
Server onlyAnswer keys and scoring logic, role and authorization checks, speaker notes and instructor-only contentOnly the Worker’s codeNever bundled into the site; served only through protected APIs to authorized roles
Persistent dataD1: accounts, sign-in sessions, progress, submissions, assessment resultsOnly through the Worker APISchema-validated input, parameterized queries, deny-by-default permissions

Warning

Hiding a button or page in the front end is not a security control. Anyone can read and change the code running in a browser, so the real authorization boundary is the Worker: every protected request has its identity and role checked again on the server.

Deployment

NTPU AI Learn has three deployment environments, each responsible for a different stage of development and release.

How a request reaches a deployed environment
  • User browser

    • Public page requests leads to Static Assets
    • API requests (/api/) leads to Worker Runtime
  • Static Assets

    Prerendered public pages

  • Worker Runtime

    Runs the API, validation and authorization

    • Upload and signed download leads to R2 file storage
    • Read and write leads to D1 database
  • R2 file storage

    Evidence files and teacher exports

  • D1 database

Three deployment environments
EnvironmentPurpose
ProductionServes live traffic
StagingIntegration and verification before a production release
Local developmentDevelopment, testing and debugging

Production and Staging use independent deployments and data. Local development uses locally simulated resources and never touches production data directly.

Failure & Degradation

This covers only the scenarios that can currently be verified against the architecture and the code's actual behavior. No documented rollback or incident procedure for a failed migration, or for the identity/session service itself degrading, was found in this repository, so no unverified procedure is listed here.

  1. Static delivery available, Worker API unavailable
    Public course content keeps reading normally through Static Assets, unaffected. Interactive operations that need the Worker (signing in, submitting an assessment, syncing progress) fail and show a clear error rather than pretending to succeed.
  2. Worker API available, D1 unavailable
    Public content is unaffected, since it is served from Static Assets and never reads the database. Interactive operations that need to read or write the database fail, and never return a false success.
  3. Worker API available, R2 file storage unavailable
    Public content and other operations that do not involve files are unaffected. Uploading or downloading credential evidence files, or downloading teacher slide exports, fails and shows a clear error, and never returns a false success.
  4. JavaScript fails to execute
    Public course content remains readable, because it is HTML generated at build time. Only the interactions React adds after it loads (such as the language switch, self-checks, or marking a lesson complete) become unavailable.

URLs and language

The platform has a single route table: client routing, the Worker’s redirects and 404s, prerendered pages and the sitemap all come from one definition. The table also puts every URL in one of three classes: public content (prerendered and indexable), public interactive workflows (not prerendered, not indexed) and protected pages that require authorization.

How the URL sets the language
URLLanguageExamples
No language prefixTraditional Chinese (default)/courses, /docs/architecture
Under /en/English/en/courses, /en/docs/architecture
Legacy /zh/ URLsPermanently redirected to the unprefixed Chinese URL/zh/courses → /courses

The URL decides the language, and switching languages keeps you on the same logical page. Because both languages share the same IDs, progress and links keep working when you switch.

Architecture Decisions

Architecturally significant decisions are kept as Architecture Decision Records (ADRs), each recording its context, the approach taken and its consequences.

ADRs are engineering documents kept in the repository, for anyone who needs a deeper understanding of the implementation or is joining development.

Was this article helpful?

Feedback is anonymous: only a daily count per article, language and answer is kept.