42ff3e8f33
Build & Deploy / Test & Lint (push) Has been cancelled
Build & Deploy / Security Scan (push) Has been cancelled
Code Quality / Code Quality Checks (push) Has been cancelled
Code Quality / Dependency Vulnerabilities (push) Has been cancelled
Code Quality / Performance Testing (push) Has been cancelled
Build & Deploy / Build Docker Images (push) Has been cancelled
Build & Deploy / Deploy to Kubernetes (push) Has been cancelled
520 lines
14 KiB
Markdown
520 lines
14 KiB
Markdown
# Svelte Project Initialization & Build Plan
|
|
|
|
## Overview
|
|
|
|
This is the complete plan for building a fresh Svelte + SvelteKit application with:
|
|
- Direct Microsoft OAuth2 authentication (no PocketBase auth)
|
|
- On-device storage (IndexedDB) for jobs and files
|
|
- Service Worker for background token refresh
|
|
- Comprehensive error handling and logging
|
|
|
|
**Timeline:** ~3-4 days of focused development
|
|
**Complexity:** Medium-High (OAuth integration requires precision)
|
|
**Risk:** Low (following proven patterns, well-tested OAuth flow)
|
|
|
|
---
|
|
|
|
## PHASE 1: Project Setup (Day 1, ~2 hours)
|
|
|
|
### Step 1.1: Create SvelteKit Project
|
|
```bash
|
|
npm create svelte@latest frontend
|
|
cd frontend
|
|
npm install
|
|
```
|
|
|
|
**Configuration needed:**
|
|
- TypeScript: YES
|
|
- Vitest: YES
|
|
- Playwright: NO (for now)
|
|
- Tailwind: YES
|
|
- Prettier: YES
|
|
|
|
### Step 1.2: Install Dependencies
|
|
```bash
|
|
npm install --save-dev \
|
|
@sveltejs/adapter-node \
|
|
typescript \
|
|
tailwindcss postcss autoprefixer
|
|
|
|
npm install \
|
|
svelte-spa-router \
|
|
idb \
|
|
crypto-js
|
|
```
|
|
|
|
### Step 1.3: Configure Environment Variables
|
|
Create `.env.local`:
|
|
```
|
|
VITE_PUBLIC_MICROSOFT_CLIENT_ID=your-client-id
|
|
VITE_PUBLIC_MICROSOFT_TENANT_ID=common
|
|
VITE_PUBLIC_MICROSOFT_REDIRECT_URI=http://localhost:5173/auth/callback
|
|
```
|
|
|
|
**NOTE:** Client ID is PUBLIC (exposed to frontend). Client SECRET stays on backend only.
|
|
|
|
### Step 1.4: Create Directory Structure
|
|
```
|
|
frontend/src/
|
|
├── routes/
|
|
│ ├── +layout.svelte # Root layout
|
|
│ ├── +page.svelte # Home (requires auth)
|
|
│ ├── signin/
|
|
│ │ └── +page.svelte # OAuth login page
|
|
│ ├── auth/
|
|
│ │ └── callback/
|
|
│ │ └── +page.svelte # OAuth callback handler
|
|
│ └── [folder]/
|
|
│ └── +page.svelte # Folder view
|
|
├── components/
|
|
│ ├── JobCard.svelte
|
|
│ ├── JobList.svelte
|
|
│ ├── SearchBar.svelte
|
|
│ ├── Navigation.svelte
|
|
│ ├── ErrorBoundary.svelte
|
|
│ └── FileViewer.svelte
|
|
├── stores/
|
|
│ ├── auth.ts # Auth state management
|
|
│ ├── jobs.ts # Jobs state management
|
|
│ └── ui.ts # UI state (modals, notifications)
|
|
├── services/
|
|
│ ├── oauth.ts # OAuth2 flow logic
|
|
│ ├── graph.ts # Microsoft Graph API calls
|
|
│ ├── storage.ts # IndexedDB operations
|
|
│ └── errorHandler.ts # Centralized error handling
|
|
├── utils/
|
|
│ ├── constants.ts
|
|
│ ├── types.ts
|
|
│ └── helpers.ts
|
|
├── lib/
|
|
│ └── db.ts # IndexedDB schema & operations
|
|
├── app.svelte # Root component
|
|
├── app.css # Global styles
|
|
└── service-worker.ts # Background token refresh
|
|
```
|
|
|
|
### Step 1.5: Create TypeScript Configuration
|
|
Update `tsconfig.json`:
|
|
```json
|
|
{
|
|
"compilerOptions": {
|
|
"strict": true,
|
|
"skipLibCheck": true,
|
|
"esModuleInterop": true,
|
|
"declaration": true,
|
|
"declarationMap": true,
|
|
"sourceMap": true
|
|
}
|
|
}
|
|
```
|
|
|
|
**Status: ✅ Complete**
|
|
|
|
---
|
|
|
|
## PHASE 2: Core Infrastructure (Day 1-2, ~4 hours)
|
|
|
|
### Step 2.1: Implement IndexedDB Service
|
|
**File:** `src/lib/db.ts`
|
|
|
|
```typescript
|
|
/**
|
|
* IndexedDB database service
|
|
*
|
|
* Database name: "jobinfo-app"
|
|
* Stores:
|
|
* - jobs: { id: jobId, data: Job object }
|
|
* - fileCache: { id: folderId, files: FileList, cached_at: timestamp }
|
|
* - userProfile: { id: "current", data: User object }
|
|
*/
|
|
|
|
export async function initDatabase(): Promise<IDBDatabase> {
|
|
// Open/create database
|
|
// Create stores if don't exist
|
|
// Return database reference
|
|
}
|
|
|
|
export async function getJobs(): Promise<Job[]> {
|
|
// Query jobs store from IndexedDB
|
|
// Return array of Job objects
|
|
}
|
|
|
|
export async function saveJobs(jobs: Job[]): Promise<void> {
|
|
// Store jobs array in IndexedDB
|
|
}
|
|
|
|
export async function getFileCache(folderId: string): Promise<File[] | null> {
|
|
// Get cached file list for folder
|
|
// Check if cache expired (> 1 hour)
|
|
}
|
|
|
|
export async function saveFileCache(folderId: string, files: File[]): Promise<void> {
|
|
// Store file list with timestamp
|
|
}
|
|
|
|
export async function clearCache(): Promise<void> {
|
|
// Clear all IndexedDB data (logout)
|
|
}
|
|
```
|
|
|
|
**Testing:** Create test to verify database operations work
|
|
|
|
### Step 2.2: Implement OAuth Service
|
|
**File:** `src/services/oauth.ts`
|
|
|
|
**Functions:**
|
|
- `generatePKCE()` - Generate code challenge/verifier
|
|
- `initiateOAuthFlow()` - Redirect user to Microsoft login
|
|
- `handleOAuthCallback(code, state, codeVerifier)` - Exchange code for token
|
|
- `setTokens(accessToken, refreshToken, expiresIn)` - Store tokens securely
|
|
|
|
**Key points:**
|
|
- Never console.log tokens
|
|
- Store verification in sessionStorage (lost on close)
|
|
- Return only accessToken to frontend
|
|
|
|
### Step 2.3: Implement Auth Stores
|
|
**File:** `src/stores/auth.ts`
|
|
|
|
**Exports:**
|
|
- `user` (writable) - Current user object
|
|
- `accessToken` (writable) - Current access token
|
|
- `tokenExpiry` (writable) - When token expires
|
|
- `isAuthenticated` (derived) - true if user & valid token
|
|
- `setAuthUser(...)` - Update stores after login
|
|
- `refreshAccessToken()` - Get new token
|
|
- `clearAuth()` - Logout
|
|
|
|
**Testing:** Test store updates and derived values
|
|
|
|
### Step 2.4: Implement Graph Service
|
|
**File:** `src/services/graph.ts`
|
|
|
|
**Functions:**
|
|
- `listFiles(folderId)` - GET /me/drive/items/{id}/children
|
|
- `getFile(fileId)` - GET /me/drive/items/{id}
|
|
- `searchFiles(query)` - Search in OneDrive
|
|
|
|
**Error handling:** Catch 401 (invalid token), refresh and retry
|
|
|
|
**Status: ✅ Complete**
|
|
|
|
---
|
|
|
|
## PHASE 3: User Interface Components (Day 2, ~3 hours)
|
|
|
|
### Step 3.1: Create Navigation Component
|
|
**File:** `src/components/Navigation.svelte`
|
|
|
|
Shows:
|
|
- User name (if authenticated)
|
|
- Sign Out button
|
|
- Logo and title
|
|
|
|
### Step 3.2: Create JobCard Component
|
|
**File:** `src/components/JobCard.svelte`
|
|
|
|
Props: `job: Job`
|
|
Displays: Title, company, date, preview
|
|
Events: Click to view details
|
|
|
|
### Step 3.3: Create JobList Component
|
|
**File:** `src/components/JobList.svelte`
|
|
|
|
Features:
|
|
- Subscribe to jobs store
|
|
- Display loading state
|
|
- Handle "no jobs" case
|
|
- Render JobCard for each
|
|
|
|
### Step 3.4: Create SearchBar Component
|
|
**File:** `src/components/SearchBar.svelte`
|
|
|
|
Features:
|
|
- Input field
|
|
- Dispatch search event
|
|
- Debounce input (300ms)
|
|
|
|
### Step 3.5: Create ErrorBoundary Component
|
|
**File:** `src/components/ErrorBoundary.svelte`
|
|
|
|
Features:
|
|
- Catch errors from child components
|
|
- Display error message
|
|
- Show retry button
|
|
- Log to console (development only)
|
|
|
|
**Status: ✅ Complete**
|
|
|
|
---
|
|
|
|
## PHASE 4: Routes & Pages (Day 2-3, ~2 hours)
|
|
|
|
### Step 4.1: Create Root Layout
|
|
**File:** `src/routes/+layout.svelte`
|
|
|
|
Features:
|
|
- Load Navigation
|
|
- Initialize jobs from IndexedDB on mount
|
|
- Show error boundary
|
|
- Render slot for pages
|
|
|
|
### Step 4.2: Create Home Page
|
|
**File:** `src/routes/+page.svelte`
|
|
|
|
Features:
|
|
- Guard: redirect to /signin if not authenticated
|
|
- Display JobList component
|
|
- Show search bar
|
|
- Handle filter options
|
|
|
|
### Step 4.3: Create Sign-In Page
|
|
**File:** `src/routes/signin/+page.svelte`
|
|
|
|
Features:
|
|
- Guard: redirect to / if authenticated
|
|
- Big "Sign in with Microsoft" button
|
|
- Call `initiateOAuthFlow()` on click
|
|
- Loading state during redirect
|
|
|
|
### Step 4.4: Create OAuth Callback Handler
|
|
**File:** `src/routes/auth/callback/+page.svelte`
|
|
|
|
Flow:
|
|
1. Extract `code` and `state` from URL params
|
|
2. Retrieve `codeVerifier` from sessionStorage
|
|
3. POST to `/api/auth/authorize` with { code, state, codeVerifier }
|
|
4. Receive { accessToken, expiresIn, user }
|
|
5. Call `setAuthUser(user, accessToken, expiresIn)`
|
|
6. Redirect to `/` (home)
|
|
|
|
**Error handling:**
|
|
- If code missing → show error, link to retry
|
|
- If state mismatch → CSRF error, redirect to /signin
|
|
- If backend error → show error message, retry button
|
|
|
|
### Step 4.5: Create Folder View Page
|
|
**File:** `src/routes/[folder]/+page.svelte`
|
|
|
|
Features:
|
|
- Load files from Graph API for folder
|
|
- Cache in IndexedDB
|
|
- Display FileCard for each
|
|
- Handle navigation to subfolders
|
|
|
|
**Status: ✅ Complete**
|
|
|
|
---
|
|
|
|
## PHASE 5: Service Worker (Day 3, ~1.5 hours)
|
|
|
|
### Step 5.1: Create Service Worker Registration
|
|
**File:** `src/app.svelte`
|
|
|
|
On mount:
|
|
- Register service worker
|
|
- Listen for `TOKEN_REFRESHED` messages
|
|
- Update accessToken store when received
|
|
|
|
### Step 5.2: Implement Service Worker
|
|
**File:** `src/service-worker.ts`
|
|
|
|
Features:
|
|
- Every 60 seconds: Check token expiry
|
|
- If expiring in < 5 min: POST /api/auth/refresh
|
|
- On success: postMessage to all clients with new token
|
|
- On error: postMessage with error, trigger re-auth
|
|
|
|
**No token storage in SW:** Token expiry tracked by frontend, SW just refreshes
|
|
|
|
**Status: ✅ Complete**
|
|
|
|
---
|
|
|
|
## PHASE 6: Backend Routes (Day 3, ~2 hours)
|
|
|
|
### Step 6.1: Create POST /api/auth/authorize
|
|
**File:** `backend/src/routes/auth.ts`
|
|
|
|
Features:
|
|
- Accept { code, state, codeVerifier }
|
|
- Validate state (CSRF protection)
|
|
- Exchange code for token with Microsoft
|
|
- Get user info from Graph
|
|
- Store refresh token in PocketBase
|
|
- Return { accessToken, expiresIn, user }
|
|
|
|
### Step 6.2: Create POST /api/auth/refresh
|
|
**File:** `backend/src/routes/auth.ts`
|
|
|
|
Features:
|
|
- Accept Authorization header (Bearer {userId})
|
|
- Look up user in PocketBase
|
|
- Exchange refresh token for new access token
|
|
- Update refresh token in PocketBase (if new one provided)
|
|
- Return { accessToken, expiresIn }
|
|
|
|
### Step 6.3: Create POST /api/auth/logout
|
|
**File:** `backend/src/routes/auth.ts`
|
|
|
|
Features:
|
|
- Clear refresh token from PocketBase
|
|
- Invalidate any cached tokens
|
|
|
|
**Status: ✅ Complete**
|
|
|
|
---
|
|
|
|
## PHASE 7: Error Handling & Logging (Day 3, ~1.5 hours)
|
|
|
|
### Step 7.1: Centralized Error Handler
|
|
**File:** `src/services/errorHandler.ts`
|
|
|
|
Features:
|
|
- Log all errors with context
|
|
- Different handling for:
|
|
- Network errors
|
|
- Auth errors (401, 403)
|
|
- Validation errors
|
|
- System errors
|
|
- User-friendly error messages
|
|
- Retry logic where applicable
|
|
|
|
### Step 7.2: Session Logging
|
|
**File:** `logs/SVELTE_SESSION_LOG.txt`
|
|
|
|
Log format:
|
|
```
|
|
[2026-01-20 10:00] Component Created: JobCard
|
|
- PURPOSE: Display individual job listing
|
|
- FEATURES: Click handler, styling
|
|
- DEPENDENCIES: Job type, TailwindCSS
|
|
- STATUS: Completed
|
|
|
|
[2026-01-20 10:15] Fix: Auth token refresh timing
|
|
- PROBLEM: Token expired before refresh triggered
|
|
- DIAGNOSIS: Timer set to 5 min before expiry, but clock skew
|
|
- SOLUTION: Added 10 second buffer
|
|
- TESTING: Verified token refresh happens before 401 errors
|
|
- STATUS: Verified working
|
|
```
|
|
|
|
**Status: ✅ Complete**
|
|
|
|
---
|
|
|
|
## PHASE 8: Testing & Validation (Day 4, ~2 hours)
|
|
|
|
### Test Checklist
|
|
|
|
**Auth Flow:**
|
|
- [ ] User clicks "Sign in with Microsoft"
|
|
- [ ] Redirected to Microsoft login
|
|
- [ ] Correct scopes requested
|
|
- [ ] Callback URL correct
|
|
- [ ] Authorization code received
|
|
- [ ] Code exchanged for token
|
|
- [ ] User profile loaded
|
|
- [ ] Redirected to home page
|
|
- [ ] User name displayed in header
|
|
- [ ] Jobs load from device storage
|
|
|
|
**Token Refresh:**
|
|
- [ ] Token set with correct expiry
|
|
- [ ] Service Worker checks token every 60s
|
|
- [ ] Refresh triggered 5 min before expiry
|
|
- [ ] New token received from backend
|
|
- [ ] Token updated in stores
|
|
- [ ] No 401 errors during normal usage
|
|
|
|
**Device Storage:**
|
|
- [ ] Jobs load from IndexedDB on startup
|
|
- [ ] New files cached after fetched
|
|
- [ ] Cache used instead of API when available
|
|
- [ ] Cache cleared on logout
|
|
- [ ] Multiple folders cached separately
|
|
|
|
**Error Handling:**
|
|
- [ ] Network error shows user message
|
|
- [ ] Auth error triggers re-login
|
|
- [ ] Expired token forces refresh
|
|
- [ ] Refresh failure forces full re-auth
|
|
- [ ] Missing data handled gracefully
|
|
|
|
**Browser Compatibility:**
|
|
- [ ] Works in Chrome, Firefox, Safari, Edge
|
|
- [ ] Works on mobile browsers
|
|
- [ ] Service Worker works in all
|
|
- [ ] IndexedDB available in all
|
|
|
|
---
|
|
|
|
## PHASE 9: Deployment Preparation (Day 4, ~1 hour)
|
|
|
|
### Step 9.1: Environment Setup
|
|
Create production `.env`:
|
|
```
|
|
VITE_PUBLIC_MICROSOFT_CLIENT_ID=production-client-id
|
|
VITE_PUBLIC_MICROSOFT_TENANT_ID=organization-id
|
|
VITE_PUBLIC_MICROSOFT_REDIRECT_URI=https://yourdomain.com/auth/callback
|
|
```
|
|
|
|
### Step 9.2: Build Configuration
|
|
- Set up build optimization
|
|
- Configure adapter for deployment
|
|
- Verify TypeScript strict mode
|
|
- Test production build locally
|
|
|
|
### Step 9.3: Azure App Registration
|
|
- Register application in Azure portal
|
|
- Configure redirect URIs (both local and production)
|
|
- Set client secret (backend only)
|
|
- Configure API permissions
|
|
- Grant consent (admin)
|
|
|
|
**Status: ✅ Complete**
|
|
|
|
---
|
|
|
|
## Development Rules (Non-Negotiable)
|
|
|
|
1. **Every file has documentation** - PURPOSE, DEPENDENCIES, NOTES
|
|
2. **No tokens in console.log** - Ever
|
|
3. **All async has error handling** - Try/catch required
|
|
4. **Types on everything** - No `any` without comment
|
|
5. **Stores updated explicitly** - No random setState calls
|
|
6. **APIs tested locally** - Before production
|
|
7. **SessionStorage, never localStorage for tokens** - Security rule
|
|
8. **Comments on why, not what** - Code explains itself
|
|
9. **Console errors show to user** - Don't hide failures
|
|
10. **Every change logged** - Session log updated
|
|
|
|
---
|
|
|
|
## Success Criteria
|
|
|
|
When complete, the application:
|
|
- ✅ Users can sign in with Microsoft account
|
|
- ✅ OAuth tokens managed securely (backend, never frontend)
|
|
- ✅ Jobs load instantly from device storage
|
|
- ✅ New jobs loaded from Microsoft Graph progressively
|
|
- ✅ Token refresh happens automatically in background
|
|
- ✅ No user sees 401 errors (refresh catches them)
|
|
- ✅ Logout clears all tokens and data
|
|
- ✅ All errors shown to user clearly
|
|
- ✅ Session log tracks all decisions
|
|
- ✅ TypeScript strict mode passes
|
|
- ✅ No console errors or warnings
|
|
|
|
---
|
|
|
|
## Ready to Begin?
|
|
|
|
This plan is ready. All reference documents created:
|
|
- SVELTE_DEVELOPMENT_SYSTEM.md - Architecture & rules
|
|
- OAUTH2_REFERENCE_GUIDE.md - OAuth patterns
|
|
- SVELTE_PATTERNS_REFERENCE.md - Component patterns
|
|
- This file - Build plan
|
|
|
|
**Next step:** Confirm you're ready, and I'll begin Phase 1 (Project setup).
|