APEX Portfolio v2 — Project Reference

Waukee APEX · dashboard.dwtapex.com/v2/ · Stack: HTML + Vanilla JS + Google Apps Script + Sheets + OAuth · Last updated May 2026

Stack & Deployment

  • Frontend: Pure HTML + Vanilla JS — no build tools, no npm
  • Backend: Google Apps Script web app (REST-style doGet/doPost)
  • Database: Google Sheets (users, checkins, artifacts, rosters, playbook_progress)
  • Auth: Google OAuth 2.0 implicit flow — redirect-based, not GIS button
  • Drive: drive.file scope — create/read files the app makes
  • Server: InMotion LiteSpeed — Ashley Prieksat manages; WAF + server cache issues
  • Working dir: ~/Library/Mobile Documents/com~apple~CloudDocs/Portfolio HTMLs/

WAF Bypass — Critical Pattern

Server WAF strips HTML containing OAuth strings. All sensitive strings must be char-code encoded.

function _c(a) { return a.map(n => String.fromCharCode(n)).join(''); }

Encoded strings in every file: googleapis.com · oauth2 · access_token · Bearer · Authorization · response_type · openid

Also: LiteSpeed server cache takes 30+ min to clear. Workaround: new filenames. Ashley must configure server-side cache.

File Structure (v2/)

  • index.html — Entry; SSO routing + course-picker onboarding
  • auth-callback.html — OAuth token handler; Drive folders; Sheet sync
  • Dashboard.html — Student dashboard; standards progress + playbooks
  • Checkin.html — Daily standup log
  • Standard1.html — Servant Leadership (kanban)
  • Standard2.html — Beyond GPA (kanban)
  • Standard3.html — Career Exploration (sidebar/process flow)
  • Standard5.html — Professional Skills (kanban)
  • instructor/index.html — Instructor dashboard; roster + heatmap
  • js/config.js — APEX_CONFIG: SCRIPT_URL, CURRENT_YEAR, SPREADSHEET_ID, OAUTH_CLIENT_ID
  • js/auth.js — OAuth redirect builder; getCurrentUser(); isInstructor()
  • js/api.js — ApexAPI: all Sheet read/write calls
  • js/apex-kanban-v2.js — initKanban(); course-aware storage; dual-enrollment
  • APPS_SCRIPT.js — Source of truth for Apps Script (redeploy after changes)
  • .htaccess — Browser cache headers (no-cache HTML, 5-min JS)

Auth Flow

  1. index.html calls getCurrentUser() from auth.js
  2. No valid session → redirect to Google OAuth (char-code URL)
  3. Google → auth-callback.html with #access_token=...
  4. Callback fetches /userinfo, preserves existing session role
  5. Creates Drive folders (student-associate, first login only)
  6. Calls ensureUser → then getUser to read Sheet role
  7. Unwrap: const profile = (raw && raw.data) ? raw.data : raw
  8. Caches 55 min → redirects to destination (from state param)
  9. Back in index.html: instructor → instructor/, no courseIds → course picker, else → Dashboard

ApexAPI Methods (api.js)

  • getUser(userId) — returns {status,data}; use .data
  • ensureUser(user) — upsert; Apps Script preserves role + drive_folder_id
  • getArtifacts(userId)
  • saveArtifact(artifact)
  • getCheckins(userId)
  • saveCheckin(checkin)
  • getPlaybookProgress(playbook, projectId, courseId)
  • savePlaybookProgress(playbook, projectId, name, stateJson, courseId)
  • getInstructorRoster(instructorId)
  • getClassProgress(courseId, schoolYear)
  • getHeatmapData(instructorId)
  • getRepository() / submitToRepository(artifact)

All responses may be array or {status:'ok',data:[...]} — handle both.

Apps Script Key Logic

  • saveUser: preserves role (never downgrade instructor) and drive_folder_id
  • Auto-roster: when student signs up, finds instructor with matching course_ids, inserts roster row
  • getClassProgress: joins rosters + artifact stats + checkin data for instructor dashboard
  • Playbook isolation: unique key = user_id + playbook + project_id + course_id (dual-enrollment safe)
  • Instructor role: set manually in Sheet col D; never overwritten by saveUser

After any APPS_SCRIPT.js change: Copy to Apps Script editor → Deploy → New deployment → Web app → Execute as Me, Access Anyone.

Kanban (apex-kanban-v2.js)

  • Call: initKanban({ ..., courseId: _user.courseId })
  • Storage key: storageKey + '_' + courseId (dual-enrollment isolation)
  • Migration: seeds from base key if course-scoped key empty (backward compat)
  • Dual-enrolled: auto renders course selector chips; window.kanbanSwitchCourse(courseId)
  • Debounced API sync on every card move

Google Drive Integration

  • Scope: drive.file — only files the app creates
  • Triggered: student-associate, first login only (!user.drive_folder_id)
  • Creates: APEX Professional Portfolio/ with 5 standard subfolders
  • Root folder ID saved to Sheet drive_folder_id column via ensureUser
  • Test: clear localStorage.removeItem('apex_auth_v2') → fresh sign-in → check Drive

17 Courses / 5 Sectors

  • Business, Technology & Communication: BSU, DCS, DWT
  • Financial & Insurance: FBI, FIAS
  • Engineering: CL, ETR, A&E, NGE, Auto
  • Human Services: CNA, EMT, RES, SPORT, EHSM
  • Bioscience & Value Added Agriculture: AVS, GFS

Not Yet Built

  • Standard4.html — pathway-specific; needs design conversation
  • Review Prep v2
  • Real Drive file upload from artifact cards (drive.file scope is ready)
  • Instructor roster management UI (add/remove students)
  • Favicon with white background (use realfavicongenerator.net)

Load Order (every page)

<script src="js/config.js"><script src="js/auth.js"><script src="js/api.js"><script src="js/apex-kanban-v2.js"> (Standards only) · All new files: WAF-encode any OAuth string.