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
392 lines
11 KiB
Markdown
392 lines
11 KiB
Markdown
# Svelte + Microsoft OAuth Development System
|
|
**Comprehensive Methodology, Standards, and Reference Tools**
|
|
|
|
---
|
|
|
|
## PHASE 1: RESEARCH & ARCHITECTURE (IN PROGRESS)
|
|
|
|
### 1. Technology Stack (Monica-Enforced Standards)
|
|
|
|
**Frontend Runtime:** Svelte (with SvelteKit for routing/build)
|
|
**Backend:** Bun + Hono (unchanged - proven working)
|
|
**Build Tool:** Vite (integrated with SvelteKit)
|
|
**Language:** TypeScript (both frontend and backend)
|
|
**Styling:** TailwindCSS + PostCSS
|
|
**Storage:** IndexedDB (device storage), PocketBase (token persistence)
|
|
**Auth:** Microsoft OAuth2 directly + Service Worker credential management
|
|
|
|
---
|
|
|
|
## PART A: MICROSOFT OAUTH2 ARCHITECTURE
|
|
|
|
### A1. Overall Flow
|
|
```
|
|
User clicks "Sign In"
|
|
→ Service Worker intercepts Microsoft OAuth redirect URL
|
|
→ Service Worker extracts auth code
|
|
→ Backend (Hono) exchanges code for tokens using Service Worker credentials
|
|
→ Backend stores refresh token in PocketBase under user collection
|
|
→ Backend returns access token to frontend
|
|
→ Frontend stores short-lived access token in memory/sessionStorage
|
|
→ Service Worker manages token refresh on expiration
|
|
```
|
|
|
|
### A2. Why This Approach
|
|
- **Direct OAuth with Microsoft:** No PocketBase auth, eliminates middle-layer complexity
|
|
- **Service Worker credentials:** Never expose client secret to frontend, server handles all token exchanges
|
|
- **PocketBase for token storage:** Persistent, synced, secure backend storage of refresh tokens
|
|
- **Device storage (IndexedDB):** User data (jobs, cache) stored locally without external service
|
|
- **Token refresh automation:** Service Worker checks token expiry and refreshes proactively
|
|
|
|
### A3. Key Endpoints Needed
|
|
```
|
|
POST /api/auth/authorize
|
|
- Accepts: { code, state }
|
|
- Returns: { accessToken, expiresIn, user }
|
|
- Action: Exchanges OAuth code for tokens via service worker credentials
|
|
|
|
POST /api/auth/refresh
|
|
- Accepts: { }
|
|
- Returns: { accessToken, expiresIn }
|
|
- Action: Uses stored refresh token to get new access token
|
|
|
|
GET /api/auth/status
|
|
- Returns: { isAuthenticated, user, tokenExpiry }
|
|
|
|
POST /api/auth/logout
|
|
- Clears refresh token from PocketBase
|
|
```
|
|
|
|
### A4. Microsoft Graph API Integration
|
|
- Endpoint: `https://graph.microsoft.com/v1.0/me/drive/root/children`
|
|
- Token required: accessToken in Authorization header
|
|
- Caching strategy: IndexedDB + device storage for file listings
|
|
- Refresh strategy: Access token → 1 hour expiry, Service Worker refreshes automatically
|
|
|
|
---
|
|
|
|
## PART B: SVELTE ARCHITECTURE
|
|
|
|
### B1. Project Structure
|
|
```
|
|
frontend/
|
|
src/
|
|
routes/
|
|
+page.svelte (home/jobs list)
|
|
signin/
|
|
+page.svelte (Microsoft OAuth login)
|
|
folder/
|
|
[id]/
|
|
+page.svelte (folder view)
|
|
components/
|
|
JobCard.svelte
|
|
FileListItem.svelte
|
|
SearchBar.svelte
|
|
stores/
|
|
auth.ts (writable stores for user, tokens)
|
|
jobs.ts (derived store for job data)
|
|
files.ts (derived store for file listings)
|
|
services/
|
|
oauth.ts (OAuth2 flow handler)
|
|
graph.ts (Microsoft Graph API calls)
|
|
storage.ts (IndexedDB operations)
|
|
utils/
|
|
constants.ts
|
|
app.svelte (layout wrapper)
|
|
svelte.config.js
|
|
vite.config.ts
|
|
tailwind.config.ts
|
|
```
|
|
|
|
### B2. Svelte Stores Strategy
|
|
```typescript
|
|
// auth.ts - Global auth state
|
|
export const user = writable<User | null>(null);
|
|
export const accessToken = writable<string | null>(null);
|
|
export const isAuthenticated = derived([user], ([$user]) => !!$user);
|
|
|
|
// jobs.ts - Derived from IndexedDB cache
|
|
export const jobs = writable<Job[]>([]);
|
|
export const jobsLoading = writable(false);
|
|
|
|
// files.ts - Derived from Microsoft Graph
|
|
export const files = writable<MicrosoftFile[]>([]);
|
|
export const filesLoading = writable(false);
|
|
```
|
|
|
|
### B3. Service Worker Role
|
|
```
|
|
Purpose: Token refresh automation + OAuth redirect interception
|
|
Tasks:
|
|
- Listens for token expiry (via postMessage from frontend)
|
|
- Automatically calls POST /api/auth/refresh before expiry
|
|
- Updates accessToken in frontend via postMessage
|
|
- Handles Microsoft OAuth redirect URL capture (if needed)
|
|
- Persists tokens in sessionStorage (never exposed to main thread)
|
|
```
|
|
|
|
---
|
|
|
|
## PART C: DEVICE STORAGE STRATEGY (IndexedDB)
|
|
|
|
### C1. Database Schema
|
|
```javascript
|
|
// Database: "jobinfo-app"
|
|
// Stores:
|
|
// 1. jobs
|
|
// Key: jobId (string)
|
|
// Value: { id, title, company, postedDate, ... }
|
|
// Index: "company", "postedDate"
|
|
|
|
// 2. fileCache
|
|
// Key: folderId (string)
|
|
// Value: { folderId, files: [...], lastRefreshed: timestamp }
|
|
// Index: "lastRefreshed"
|
|
|
|
// 3. userCache
|
|
// Key: "current"
|
|
// Value: { user object from Microsoft }
|
|
|
|
// 4. tokenMetadata (not storing tokens, just metadata)
|
|
// Key: "current"
|
|
// Value: { expiresAt: timestamp, refreshedAt: timestamp }
|
|
```
|
|
|
|
### C2. IndexedDB Helper Functions
|
|
```typescript
|
|
// storage.ts
|
|
export async function saveJobs(jobs: Job[]): Promise<void>
|
|
export async function getJobs(): Promise<Job[]>
|
|
export async function saveFileCache(folderId: string, files: MicrosoftFile[]): Promise<void>
|
|
export async function getFileCache(folderId: string): Promise<MicrosoftFile[] | null>
|
|
export async function clearExpiredCache(): Promise<void>
|
|
```
|
|
|
|
---
|
|
|
|
## PART D: BACKEND (HONO) UPDATES
|
|
|
|
### D1. New Routes
|
|
```typescript
|
|
// backend/auth-routes.ts
|
|
app.post('/auth/authorize', async (c) => {
|
|
// 1. Validate request { code, state }
|
|
// 2. Exchange code for tokens using Microsoft OAuth credentials
|
|
// 3. Store refresh token in PocketBase
|
|
// 4. Return accessToken to frontend
|
|
})
|
|
|
|
app.post('/auth/refresh', async (c) => {
|
|
// 1. Get user from auth header
|
|
// 2. Retrieve refresh token from PocketBase
|
|
// 3. Exchange for new access token
|
|
// 4. Return new access token
|
|
})
|
|
|
|
app.get('/auth/status', async (c) => {
|
|
// Return current auth status
|
|
})
|
|
```
|
|
|
|
### D2. Environment Variables Required
|
|
```
|
|
MICROSOFT_CLIENT_ID=
|
|
MICROSOFT_CLIENT_SECRET=
|
|
MICROSOFT_TENANT_ID=
|
|
MICROSOFT_REDIRECT_URI=http://localhost:5173/auth/callback
|
|
POCKETBASE_URL=https://pocketbase.ccllc.pro
|
|
POCKETBASE_ADMIN_EMAIL=
|
|
POCKETBASE_ADMIN_PASSWORD=
|
|
```
|
|
|
|
---
|
|
|
|
## PART E: DEVELOPMENT RULES (NON-NEGOTIABLE)
|
|
|
|
### RULE E1: Code Documentation Standard
|
|
Every file, every function, every store must have:
|
|
```typescript
|
|
/**
|
|
* MODULE: [Name]
|
|
* PURPOSE: [What it does]
|
|
* DEPENDENCIES: [External services, stores, APIs]
|
|
* PERSISTENCE: [How/where data is stored]
|
|
* NOTES: [Any gotchas, edge cases, security considerations]
|
|
*/
|
|
```
|
|
|
|
### RULE E2: Component Structure
|
|
Every Svelte component must have:
|
|
```svelte
|
|
<!--
|
|
COMPONENT: ComponentName
|
|
PURPOSE: [What it renders]
|
|
PROPS: [Detailed prop documentation]
|
|
EVENTS: [Events this component dispatches]
|
|
STATE: [Local reactive state]
|
|
-->
|
|
|
|
<script lang="ts">
|
|
// Props with detailed comments
|
|
// Reactive declarations
|
|
// Lifecycle hooks
|
|
// Event handlers
|
|
</script>
|
|
|
|
<style>
|
|
/* TailwindCSS classes only, no custom CSS unless documented as necessary */
|
|
</style>
|
|
```
|
|
|
|
### RULE E3: Store Pattern
|
|
Every Svelte store follows this pattern:
|
|
```typescript
|
|
/**
|
|
* STORE: storeName
|
|
* PURPOSE: [What data it holds]
|
|
* UPDATES: [How and when it's updated]
|
|
* SUBSCRIPTIONS: [Components that use it]
|
|
*/
|
|
|
|
export const storeName = writable<Type>(initialValue);
|
|
|
|
// Every update must be wrapped in a clearly named function:
|
|
export async function updateStoreName(newValue: Type): Promise<void> {
|
|
// Detailed comment explaining the update
|
|
storeName.set(newValue);
|
|
}
|
|
```
|
|
|
|
### RULE E4: API Call Pattern
|
|
Every API call to backend must follow:
|
|
```typescript
|
|
/**
|
|
* API: /endpoint
|
|
* METHOD: GET/POST/etc
|
|
* HEADERS REQUIRED: [Auth, Content-Type, etc]
|
|
* REQUEST BODY: { ... with detailed types }
|
|
* RESPONSE: { ... with detailed types }
|
|
* ERRORS: [What can go wrong and how we handle it]
|
|
* RETRY STRATEGY: [Is this retried? When?]
|
|
*/
|
|
|
|
export async function apiCallName(params: Type): Promise<ResponseType> {
|
|
const token = get(accessToken);
|
|
if (!token) throw new Error('Not authenticated');
|
|
|
|
try {
|
|
const response = await fetch(`/api/endpoint`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'Authorization': `Bearer ${token}`,
|
|
},
|
|
body: JSON.stringify(params),
|
|
});
|
|
|
|
if (!response.ok) throw new Error(`API error: ${response.status}`);
|
|
return response.json();
|
|
} catch (error) {
|
|
console.error('API call failed:', error);
|
|
throw error;
|
|
}
|
|
}
|
|
```
|
|
|
|
### RULE E5: Error Handling Standard
|
|
No silent failures. Every promise, every fetch, every async operation:
|
|
```typescript
|
|
try {
|
|
// Operation
|
|
} catch (error) {
|
|
// 1. Log error with context
|
|
console.error('Context: what were we trying to do', error);
|
|
|
|
// 2. Determine if recoverable
|
|
if (isRecoverable(error)) {
|
|
// Attempt recovery
|
|
} else {
|
|
// Notify user or escalate
|
|
}
|
|
}
|
|
```
|
|
|
|
### RULE E6: Token Handling
|
|
- **Never store tokens in localStorage** (vulnerable to XSS)
|
|
- **Access token:** In memory only (lost on refresh, that's intentional)
|
|
- **Refresh token:** Only on backend in PocketBase
|
|
- **Service Worker:** Can read from sessionStorage only, never localStorage
|
|
- **Token expiry check:** Frontend checks before each API call, Service Worker proactively refreshes
|
|
|
|
### RULE E7: Code Quality Standards
|
|
- Every TypeScript file has strict: true in tsconfig
|
|
- No `any` types without explicit comment explaining why
|
|
- No console.log in production code (use proper logging)
|
|
- All async operations have error handling
|
|
- All state changes are intentional and logged in stores
|
|
- No magic numbers or strings (use CONST_DEFINED_AT_TOP)
|
|
|
|
---
|
|
|
|
## PART F: DEVELOPMENT CHECKLIST
|
|
|
|
### Pre-Build Checklist
|
|
- [ ] All imports are TypeScript (.ts, not .js)
|
|
- [ ] All stores have update functions with documentation
|
|
- [ ] All API calls have error handling
|
|
- [ ] All tokens are handled per RULE E6
|
|
- [ ] All components follow component structure in RULE E2
|
|
- [ ] No hardcoded URLs (use constants)
|
|
- [ ] No console.log statements
|
|
- [ ] All environment variables are documented
|
|
|
|
### Testing Checklist
|
|
- [ ] Login flow works (OAuth → token storage → API calls)
|
|
- [ ] Token refresh works (Service Worker updates token)
|
|
- [ ] File listing loads from Microsoft Graph
|
|
- [ ] File listing cached in IndexedDB
|
|
- [ ] Search/filter works from cached files
|
|
- [ ] Logout clears tokens and cache
|
|
- [ ] Offline mode works (uses cached data)
|
|
|
|
---
|
|
|
|
## PART G: SESSION LOG LOCATION
|
|
|
|
All work logged to: `/home/admin/Job-Info-Test/logs/SVELTE_SESSION_LOG.txt`
|
|
|
|
Format:
|
|
```
|
|
[2026-01-19 10:00] Component/Store Created: ComponentName
|
|
- PURPOSE: [What it does]
|
|
- IMPLEMENTATION: [Key decisions]
|
|
- DEPENDENCIES: [What it depends on]
|
|
- STATUS: Completed / In Progress / Blocked
|
|
|
|
[2026-01-19 10:15] Fix: [Issue resolved]
|
|
- PROBLEM: [What was broken]
|
|
- DIAGNOSIS: [How we found the issue]
|
|
- SOLUTION: [What we changed]
|
|
- TESTING: [How we verified it works]
|
|
- STATUS: Verified working
|
|
```
|
|
|
|
---
|
|
|
|
## NEXT STEPS
|
|
|
|
1. ✅ Create this document (DONE)
|
|
2. ⏳ Research Svelte + SvelteKit best practices
|
|
3. ⏳ Research Microsoft OAuth2 + Service Worker patterns
|
|
4. ⏳ Create backend OAuth routes
|
|
5. ⏳ Create Svelte project structure
|
|
6. ⏳ Create auth store and OAuth service
|
|
7. ⏳ Create IndexedDB service
|
|
8. ⏳ Create Microsoft Graph service
|
|
9. ⏳ Build UI components
|
|
10. ⏳ Integrate and test end-to-end
|
|
|
|
---
|
|
|
|
**Status: FOUNDATION DOCUMENT CREATED - Ready for Research Phase**
|