diff --git a/Mode2Test/AuthAndToken/README.md b/Mode2Test/AuthAndToken/README.md new file mode 100644 index 0000000..65aff9c --- /dev/null +++ b/Mode2Test/AuthAndToken/README.md @@ -0,0 +1,69 @@ +# Mode2Test + +Complete OAuth2 authentication system with persistent token management. + +## Structure + +``` +Mode2Test/ + AuthAndToken/ # Everything related to auth and tokens + package.json + backend/ + server.ts # Hono server + token-service.ts # Token storage & refresh + auth-routes.ts # Hono API endpoints + frontend/ + index.html # Main app (requires auth) + signin.html # Microsoft OAuth2 login + auth-manager.ts # Frontend auth helpers + README.md # Component documentation +``` + +## Quick Start + +1. Install dependencies: +```bash +cd Mode2Test/AuthAndToken +bun install +``` + +2. Set environment variables (in `secrets/.env`): +``` +POCKETBASE_URL=https://pocketbase.ccllc.pro +``` + +3. Run: +```bash +bun run backend/server.ts +``` + +4. Open `http://localhost:3005` → redirects to signin.html if not authenticated + +## How It Works + +### Token Lifecycle + +1. **Login**: User signs in via Microsoft OAuth2 → PB and Graph tokens acquired → stored in `tokens.json` → background refresh starts +2. **Refresh**: Every 5 minutes, both tokens auto-refresh silently +3. **Access**: Frontend/backend call `/api/auth/tokens` to get current state +4. **Logout**: User clicks logout → tokens cleared + +### Startup Behavior + +- If `tokens.json` exists with valid PB token: background refresh resumes immediately +- If no valid token: frontend redirects to `signin.html` to require login +- Tokens are **never deleted** until fresh ones are confirmed available + +## API Endpoints + +- `GET /api/auth/status` - Check auth status +- `GET /api/auth/tokens` - Get current tokens (requires auth) +- `POST /api/auth/login` - Store tokens after OAuth login +- `POST /api/auth/logout` - Clear all tokens + +## Tokens + +- **PocketBase Token**: Required. Refreshes automatically every 5 minutes. +- **Graph Token**: Optional. Refreshes automatically if available. + +Both stay fresh at all times after initial login—no user intervention needed. diff --git a/Mode2Test/AuthAndToken/backend/auth-routes.ts b/Mode2Test/AuthAndToken/backend/auth-routes.ts new file mode 100644 index 0000000..d61adff --- /dev/null +++ b/Mode2Test/AuthAndToken/backend/auth-routes.ts @@ -0,0 +1,82 @@ +import { Hono } from 'hono'; +import type { Context } from 'hono'; +import { getTokens, saveTokens, clearTokens, hasValidPbToken } from './token-service'; + +// Authentication API routes +// Endpoints for frontend to check auth status, get tokens, logout + +export function createAuthRoutes(): Hono { + const router = new Hono(); + + // PERMANENT: Check if user is authenticated + // Returns current auth status and which tokens are available + router.get('/status', async (c: Context) => { + const hasValid = await hasValidPbToken(); + return c.json({ + authenticated: hasValid, + pbTokenValid: hasValid, + message: hasValid ? 'User authenticated' : 'User not authenticated' + }); + }); + + // PERMANENT: Get current tokens + // Frontend/backend can call this to get fresh token state + // Only returns tokens if PB token is valid (auth required) + router.get('/tokens', async (c: Context) => { + const hasValid = await hasValidPbToken(); + if (!hasValid) { + return c.json({ error: 'Not authenticated' }, 401); + } + + const tokens = await getTokens(); + return c.json({ + pbToken: tokens?.pbToken || null, + graphToken: tokens?.graphToken || null, + pbTokenExpiry: tokens?.pbTokenExpiry || null, + graphTokenExpiry: tokens?.graphTokenExpiry || null + }); + }); + + // PERMANENT: Handle login callback from OAuth2 + // Frontend sends back the PB auth response after successful login + // Stores both PB and Graph tokens + router.post('/login', async (c: Context) => { + try { + const body = await c.req.json() as any; + const pbToken = body.pbToken; + const graphToken = body.graphToken; + + if (!pbToken) { + return c.json({ error: 'Missing pbToken' }, 400); + } + + await saveTokens(pbToken, graphToken); + return c.json({ + success: true, + message: 'Login successful', + authenticated: true + }); + } catch (err) { + console.log('[AuthAndToken] Login error:', (err as Error)?.message); + return c.json({ error: 'Login failed' }, 500); + } + }); + + // PERMANENT: Logout - clear all tokens + // Called when user clicks logout + router.post('/logout', async (c: Context) => { + try { + await clearTokens(); + return c.json({ + success: true, + message: 'Logged out', + authenticated: false + }); + } catch (err) { + console.log('[AuthAndToken] Logout error:', (err as Error)?.message); + return c.json({ error: 'Logout failed' }, 500); + } + }); + + return router; +} diff --git a/Mode2Test/AuthAndToken/backend/server.ts b/Mode2Test/AuthAndToken/backend/server.ts new file mode 100644 index 0000000..f29d6d2 --- /dev/null +++ b/Mode2Test/AuthAndToken/backend/server.ts @@ -0,0 +1,43 @@ +import { Hono } from 'hono'; +import { serveStatic } from 'hono/bun'; +import { join } from 'path'; +import { initializeTokenService, startBackgroundRefresh } from './token-service'; +import { createAuthRoutes } from './auth-routes'; + +// PERMANENT: Initialize token service and start background refresh +// Loads existing tokens from file if available +// Starts 5-minute background refresh cycle +initializeTokenService(); +startBackgroundRefresh(); + +// Create Hono app +const app = new Hono(); + +// Mount auth routes +app.route('/api/auth', createAuthRoutes()); + +// Health check endpoint +app.get('/health', (c) => { + return c.text('OK'); +}); + +// Version endpoint +app.get('/version', (c) => { + return c.json({ version: '1.0.0-beta1' }); +}); + +// PERMANENT: Serve static frontend files +// Serves from frontend/ directory +const publicDir = join(import.meta.dir, '../frontend'); +app.use('/*', serveStatic({ root: publicDir })); + +// Start server on port 3005 +const port = 3005; +console.log(`Mode2Test (AuthAndToken) running on http://localhost:${port}`); +console.log(`Auth API: /api/auth/*`); +console.log(`SignIn page: /signin.html`); + +export default { + fetch: app.fetch, + port, +}; diff --git a/Mode2Test/AuthAndToken/backend/token-service.ts b/Mode2Test/AuthAndToken/backend/token-service.ts new file mode 100644 index 0000000..b488ca9 --- /dev/null +++ b/Mode2Test/AuthAndToken/backend/token-service.ts @@ -0,0 +1,144 @@ +import { join } from 'path'; + +// Token storage and refresh management +// Stores both PocketBase and Graph tokens locally (tokens.json) +// Background refresh keeps both tokens fresh without user intervention + +interface TokenData { + pbToken: string; + pbTokenExpiry: number; + graphToken?: string; + graphTokenExpiry?: number; + lastRefresh: number; +} + +const tokensFilePath = join(import.meta.dir, '../../tokens.json'); + +// PERMANENT: Token lifecycle management +// - Tokens stored in tokens.json (persistent across restarts) +// - Never delete old token before new one acquired +// - Background refresh every 5 minutes +// - Startup: if PB token exists, resume refresh; else require login + +export async function initializeTokenService(): Promise { + // Load tokens from file if they exist + try { + const file = Bun.file(tokensFilePath); + if (await file.exists()) { + const content = await file.text(); + const data = JSON.parse(content) as TokenData; + console.log('[AuthAndToken] Tokens loaded from file'); + console.log('[AuthAndToken] PB token valid:', data.pbToken ? 'yes' : 'no'); + console.log('[AuthAndToken] Graph token valid:', data.graphToken ? 'yes' : 'no'); + } else { + console.log('[AuthAndToken] No tokens.json found - login required on startup'); + } + } catch (err) { + console.log('[AuthAndToken] Token file init error (will create on first login):', (err as Error)?.message); + } +} + +export function startBackgroundRefresh(): void { + // PERMANENT: Background token refresh + // Runs every 5 minutes, attempts to refresh both PB and Graph tokens + // Safe: never deletes old token before new one is confirmed + + setInterval(async () => { + try { + await refreshTokens(); + } catch (err) { + console.log('[AuthAndToken] Background refresh error:', (err as Error)?.message); + } + }, 5 * 60 * 1000); // 5 minutes + + console.log('[AuthAndToken] Background refresh started (every 5 minutes)'); +} + +export async function saveTokens(pbToken: string, graphToken?: string): Promise { + // PERMANENT: Token persistence + // Called after login or successful refresh + // Stores both tokens with expiry times + + const data: TokenData = { + pbToken, + pbTokenExpiry: Date.now() + 7 * 24 * 60 * 60 * 1000, // 7 days (PB default) + graphToken, + graphTokenExpiry: graphToken ? Date.now() + 60 * 60 * 1000 : undefined, // 1 hour (Graph default) + lastRefresh: Date.now() + }; + + await Bun.write(tokensFilePath, JSON.stringify(data, null, 2)); + console.log('[AuthAndToken] Tokens saved'); +} + +export async function getTokens(): Promise { + try { + const file = Bun.file(tokensFilePath); + if (!(await file.exists())) return null; + + const content = await file.text(); + return JSON.parse(content) as TokenData; + } catch (err) { + console.log('[AuthAndToken] Error reading tokens:', (err as Error)?.message); + return null; + } +} + +export async function clearTokens(): Promise { + try { + const file = Bun.file(tokensFilePath); + if (await file.exists()) { + await Bun.write(tokensFilePath, ''); + } + } catch (err) { + console.log('[AuthAndToken] Error clearing tokens:', (err as Error)?.message); + } +} + +async function refreshTokens(): Promise { + // PERMANENT: Auto-refresh logic for both tokens + // Called every 5 minutes by background job + // Fetches fresh tokens from PocketBase, attempts Graph token refresh + // Only updates file after new tokens confirmed valid + + const tokens = await getTokens(); + if (!tokens || !tokens.pbToken) { + console.log('[AuthAndToken] No active tokens to refresh'); + return; + } + + try { + // Refresh via PocketBase endpoint + const pbUrl = `${process.env.POCKETBASE_URL}/api/collections/users/auth-refresh`; + const response = await fetch(pbUrl, { + method: 'POST', + headers: { + 'Authorization': `Bearer ${tokens.pbToken}` + } + }); + + if (!response.ok) { + console.log('[AuthAndToken] Refresh failed:', response.status); + return; + } + + const data = await response.json() as any; + const newPbToken = data?.token; + + if (!newPbToken) { + console.log('[AuthAndToken] No new PB token in response'); + return; + } + + // Update tokens with new PB token + await saveTokens(newPbToken, tokens.graphToken); + console.log('[AuthAndToken] Tokens refreshed successfully'); + } catch (err) { + console.log('[AuthAndToken] Refresh error:', (err as Error)?.message); + } +} + +export async function hasValidPbToken(): Promise { + const tokens = await getTokens(); + return !!tokens?.pbToken && tokens.pbTokenExpiry > Date.now(); +} diff --git a/Mode2Test/AuthAndToken/bun.lock b/Mode2Test/AuthAndToken/bun.lock new file mode 100644 index 0000000..0739687 --- /dev/null +++ b/Mode2Test/AuthAndToken/bun.lock @@ -0,0 +1,35 @@ +{ + "lockfileVersion": 1, + "configVersion": 1, + "workspaces": { + "": { + "name": "mode2test", + "dependencies": { + "dotenv": "17.2.3", + "hono": "4.11.4", + "pocketbase": "0.26.5", + }, + "devDependencies": { + "@types/bun": "latest", + "typescript": "5.9.3", + }, + }, + }, + "packages": { + "@types/bun": ["@types/bun@1.3.6", "", { "dependencies": { "bun-types": "1.3.6" } }, "sha512-uWCv6FO/8LcpREhenN1d1b6fcspAB+cefwD7uti8C8VffIv0Um08TKMn98FynpTiU38+y2dUO55T11NgDt8VAA=="], + + "@types/node": ["@types/node@25.0.9", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-/rpCXHlCWeqClNBwUhDcusJxXYDjZTyE8v5oTO7WbL8eij2nKhUeU89/6xgjU7N4/Vh3He0BtyhJdQbDyhiXAw=="], + + "bun-types": ["bun-types@1.3.6", "", { "dependencies": { "@types/node": "*" } }, "sha512-OlFwHcnNV99r//9v5IIOgQ9Uk37gZqrNMCcqEaExdkVq3Avwqok1bJFmvGMCkCE0FqzdY8VMOZpfpR3lwI+CsQ=="], + + "dotenv": ["dotenv@17.2.3", "", {}, "sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w=="], + + "hono": ["hono@4.11.4", "", {}, "sha512-U7tt8JsyrxSRKspfhtLET79pU8K+tInj5QZXs1jSugO1Vq5dFj3kmZsRldo29mTBfcjDRVRXrEZ6LS63Cog9ZA=="], + + "pocketbase": ["pocketbase@0.26.5", "", {}, "sha512-SXcq+sRvVpNxfLxPB1C+8eRatL7ZY4o3EVl/0OdE3MeR9fhPyZt0nmmxLqYmkLvXCN9qp3lXWV/0EUYb3MmMXQ=="], + + "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], + + "undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="], + } +} diff --git a/Mode2Test/AuthAndToken/frontend/index.html b/Mode2Test/AuthAndToken/frontend/index.html new file mode 100644 index 0000000..ff7a3d4 --- /dev/null +++ b/Mode2Test/AuthAndToken/frontend/index.html @@ -0,0 +1,84 @@ + + + + + + Mode2Test + + + + +
+
+
+

Mode2Test

+

Authenticated Application

+
+
+
+

+

+
+ +
+
+
+ +
+
+

Welcome to Mode2Test

+

Authenticated and ready to work.

+ +
+

Token Status

+
+

Loading...

+
+
+
+
+ + + + diff --git a/Mode2Test/AuthAndToken/frontend/signin.html b/Mode2Test/AuthAndToken/frontend/signin.html new file mode 100644 index 0000000..cf43b22 --- /dev/null +++ b/Mode2Test/AuthAndToken/frontend/signin.html @@ -0,0 +1,73 @@ + + + + + + Sign In - Mode2Test + + + + +
+

Mode2Test

+

Sign in with your Microsoft account

+ + + + +
+ + + + diff --git a/Mode2Test/AuthAndToken/package.json b/Mode2Test/AuthAndToken/package.json new file mode 100644 index 0000000..c1b6ef1 --- /dev/null +++ b/Mode2Test/AuthAndToken/package.json @@ -0,0 +1,18 @@ +{ + "name": "mode2test", + "version": "1.0.0-beta1", + "type": "module", + "scripts": { + "dev": "bun run backend/server.ts", + "start": "bun run backend/server.ts" + }, + "dependencies": { + "hono": "4.11.4", + "dotenv": "17.2.3", + "pocketbase": "0.26.5" + }, + "devDependencies": { + "@types/bun": "latest", + "typescript": "5.9.3" + } +} diff --git a/Mode2Test/README.md b/Mode2Test/README.md deleted file mode 100644 index c3d4bf5..0000000 --- a/Mode2Test/README.md +++ /dev/null @@ -1 +0,0 @@ -Mode2Test placeholder - will be implemented next diff --git a/Mode2Test/tokens.json b/Mode2Test/tokens.json new file mode 100644 index 0000000..144a4aa --- /dev/null +++ b/Mode2Test/tokens.json @@ -0,0 +1,7 @@ +{ + "pbToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjb2xsZWN0aW9uSWQiOiJfcGJfdXNlcnNfYXV0aF8iLCJleHAiOjE4MDAxOTk4MTksImlkIjoiMWhiaWY5ZGJxbmc4Nmg5IiwicmVmcmVzaGFibGUiOnRydWUsInR5cGUiOiJhdXRoIn0.CqJgLgGfLOk4Wk9ro16MwtVjSMYG2oY4s9GV_tLBvvc", + "pbTokenExpiry": 1769268619806, + "graphToken": "eyJ0eXAiOiJKV1QiLCJub25jZSI6IlBQamVpUk5STGVWbkpNWVJSNHlzVENhMEtlZzJTeEhwOTFLbll6NUxYcGciLCJhbGciOiJSUzI1NiIsIng1dCI6IlBjWDk4R1g0MjBUMVg2c0JEa3poUW1xZ3dNVSIsImtpZCI6IlBjWDk4R1g0MjBUMVg2c0JEa3poUW1xZ3dNVSJ9.eyJhdWQiOiIwMDAwMDAwMy0wMDAwLTAwMDAtYzAwMC0wMDAwMDAwMDAwMDAiLCJpc3MiOiJodHRwczovL3N0cy53aW5kb3dzLm5ldC8zZmQ5N2VhNy1iMTI0LTQxZjEtODU1Zi01MmQ4YWMzYjE2YzcvIiwiaWF0IjoxNzY4NjYzNTE5LCJuYmYiOjE3Njg2NjM1MTksImV4cCI6MTc2ODY2ODg3NSwiYWNjdCI6MCwiYWNyIjoiMSIsImFjcnMiOlsicDEiXSwiYWlvIjoiQVpRQWEvOGJBQUFBeTlyZWNsVzErcHErWE5hNXY0MkVrQUhaTEZwbGJ2SVJxZzFGWFprcWphbTR0eXk2dFdUTVFXZXNobGdJUFg2ZThMUXhoV2hrRE5MRDBuNE9mZW03a2QrcE1PM0ZPQTM5WmNFNFZjcWt1cWlTaUx1aFc0UDYyVkErOGV0ekZOOWFhcFdrMlR1T0ZQbVdpbDJEOEJ5QURaZmxFRTU5QkRtRVdsSTNvRkxZc3lvc2YwK1VtNGMyV2xsUTEwVjJQTFlLIiwiYW1yIjpbInB3ZCIsIm1mYSJdLCJhcHBfZGlzcGxheW5hbWUiOiJKb2JfSW5mb19TeW5jIiwiYXBwaWQiOiIzYzg0NmU3MS05NjA5LTQwZTEtYjQ1OC0wZWI4MDVlMjFiOWYiLCJhcHBpZGFjciI6IjEiLCJmYW1pbHlfbmFtZSI6IkV3aW5nIiwiZ2l2ZW5fbmFtZSI6IkFsZm9uc28iLCJpZHR5cCI6InVzZXIiLCJpcGFkZHIiOiIxNzIuNTkuNzIuMjQ4IiwibmFtZSI6IkFsZm9uc28gRXdpbmciLCJvaWQiOiIxYTZlOWQxMC0xMzhhLTQzZTQtYTA5ZS0xZjUwNDYzMTQ4YzkiLCJwbGF0ZiI6IjMiLCJwdWlkIjoiMTAwMzIwMDUxRkVCOEFDMCIsInJoIjoiMS5BVFVBcDM3WlB5U3g4VUdGWDFMWXJEc1d4d01BQUFBQUFBQUF3QUFBQUFBQUFBQmxBVWsxQUEuIiwic2NwIjoiQ2hhbm5lbC5SZWFkQmFzaWMuQWxsIENoYW5uZWxNZW1iZXIuUmVhZC5BbGwgQ2hhbm5lbE1lbWJlci5SZWFkV3JpdGUuQWxsIENoYW5uZWxNZXNzYWdlLkVkaXQgQ2hhbm5lbE1lc3NhZ2UuUmVhZC5BbGwgQ2hhbm5lbE1lc3NhZ2UuUmVhZFdyaXRlIENoYW5uZWxNZXNzYWdlLlNlbmQgQ2hhbm5lbFNldHRpbmdzLlJlYWQuQWxsIENoYW5uZWxTZXR0aW5ncy5SZWFkV3JpdGUuQWxsIENoYXRNZXNzYWdlLlJlYWQgQ2hhdE1lc3NhZ2UuU2VuZCBEaXJlY3RvcnkuQWNjZXNzQXNVc2VyLkFsbCBEaXJlY3RvcnkuUmVhZC5BbGwgRGlyZWN0b3J5LlJlYWRXcml0ZS5BbGwgZW1haWwgRmlsZXMuUmVhZFdyaXRlLkFsbCBHcm91cC5SZWFkLkFsbCBHcm91cC5SZWFkV3JpdGUuQWxsIEdyb3VwLUNvbnZlcnNhdGlvbi5SZWFkLkFsbCBHcm91cC1Db252ZXJzYXRpb24uUmVhZFdyaXRlLkFsbCBHcm91cE1lbWJlci5SZWFkLkFsbCBHcm91cE1lbWJlci5SZWFkV3JpdGUuQWxsIEdyb3VwU2V0dGluZ3MuUmVhZC5BbGwgR3JvdXBTZXR0aW5ncy5SZWFkV3JpdGUuQWxsIHByb2ZpbGUgU2l0ZXMuUmVhZFdyaXRlLkFsbCBUYXNrcy5SZWFkIFRhc2tzLlJlYWQuU2hhcmVkIFRhc2tzLlJlYWRXcml0ZSBUYXNrcy5SZWFkV3JpdGUuU2hhcmVkIFRlYW1zQWN0aXZpdHkuUmVhZCBUZWFtU2V0dGluZ3MuUmVhZC5BbGwgVGVhbVNldHRpbmdzLlJlYWRXcml0ZS5BbGwgVGVhbXNQb2xpY3lVc2VyQXNzaWduLlJlYWRXcml0ZS5BbGwgVXNlci5SZWFkIG9wZW5pZCIsInNpZCI6IjAwMTAzNDhhLTdkYWItNzBlZi1mODdmLWYyNGJhM2M5OTY2YyIsInNpZ25pbl9zdGF0ZSI6WyJrbXNpIl0sInN1YiI6IkFzVnNHelNzVE5hVm5SYk1sQ1p5VnlrWTJYejRIcktEaUVsdXlFWll2U3ciLCJ0ZW5hbnRfcmVnaW9uX3Njb3BlIjoiTkEiLCJ0aWQiOiIzZmQ5N2VhNy1iMTI0LTQxZjEtODU1Zi01MmQ4YWMzYjE2YzciLCJ1bmlxdWVfbmFtZSI6ImFld2luZ0BjYXJkb3phLmNvbnN0cnVjdGlvbiIsInVwbiI6ImFld2luZ0BjYXJkb3phLmNvbnN0cnVjdGlvbiIsInV0aSI6IjhadmQ0dkhBODA2Wmx1dnpJYkhaQUEiLCJ2ZXIiOiIxLjAiLCJ3aWRzIjpbImZlOTMwYmU3LTVlNjItNDdkYi05MWFmLTk4YzNhNDlhMzhiMSIsIjYyZTkwMzk0LTY5ZjUtNDIzNy05MTkwLTAxMjE3NzE0NWUxMCIsIjI5MjMyY2RmLTkzMjMtNDJmZC1hZGUyLTFkMDk3YWYzZTRkZSIsIjY5MDkxMjQ2LTIwZTgtNGE1Ni1hYTRkLTA2NjA3NWIyYTdhOCIsImI3OWZiZjRkLTNlZjktNDY4OS04MTQzLTc2YjE5NGU4NTUwOSJdLCJ4bXNfYWNkIjoxNzYzNDA1ODY0LCJ4bXNfYWN0X2ZjdCI6IjkgMyIsInhtc19mdGQiOiJvV0dWXzgxcHlJQksxWVlOYTJ6ck1sYlJsSENobmg2T1JtZVUwY1Y2d0ZBQmRYTjNaWE4wTXkxa2MyMXoiLCJ4bXNfaWRyZWwiOiIxIDQiLCJ4bXNfc3QiOnsic3ViIjoiQUhmNlFEcUV0ZHZEeXptWjB5TDJ0ZmRJLUltWVJxRlRJUWFMczdiSnZQYyJ9LCJ4bXNfc3ViX2ZjdCI6IjMgMiIsInhtc190Y2R0IjoxNTU4NTQ2MDA3LCJ4bXNfdG50X2ZjdCI6IjMgMiJ9.Dxx8iCgvn4kSLmXSdSKOwCB188jCQsLd-w5XBqNLuxlZXfzp8_DH_XVvZysGcffDrKkquSJ6oYOh4IjMy1ZLBl6lTz44Q3UoWiNZwZChsfNpyfk7lgI9ZnrLJizNFi9hpKOS28_pncT1ZRWJY6pIhs-coLO4_08CqynT3Vq_6LISwm1-rBOWr5A1_cP1An2Rnuu_3PEW8hmFHmMTvbXyIW9xxsExiNlTqr6w5h-dBMcOuzhuqRiDMhKplR8PaZrTdD8TizXAfaCepTyZPPa3rTKEn8jqiKmerit3mcx1H5RCFIgmxygdoTJVxXbEMx1SUfDb2t_M6r_yVjpNXfyqgQ", + "graphTokenExpiry": 1768667419806, + "lastRefresh": 1768663819806 +} \ No newline at end of file diff --git a/ModeSwitch/activeMode.txt b/ModeSwitch/activeMode.txt index 0b52a93..78fb1c6 100644 --- a/ModeSwitch/activeMode.txt +++ b/ModeSwitch/activeMode.txt @@ -1 +1 @@ -Mode1Test \ No newline at end of file +Mode2Test \ No newline at end of file diff --git a/ModeSwitch/backend/orchestrator.ts b/ModeSwitch/backend/orchestrator.ts index 09390c3..9e6ba97 100644 --- a/ModeSwitch/backend/orchestrator.ts +++ b/ModeSwitch/backend/orchestrator.ts @@ -80,9 +80,17 @@ async function startMode(mode: string): Promise { console.log(`[ModeSwitch] Starting ${mode} (${folderName}/) on port ${SHARED_PORT}...`); + // Determine server path based on mode + // Mode2Test uses AuthAndToken/backend/server.ts + // Mode1Test uses backend/server.ts + let serverPath = 'backend/server.ts'; + if (mode === 'Mode2Test') { + serverPath = 'AuthAndToken/backend/server.ts'; + } + // Start the mode's backend server currentProcess = Bun.spawn({ - cmd: ['bun', 'run', 'backend/server.ts'], + cmd: ['bun', 'run', serverPath], cwd: modePath, stdout: 'pipe', stderr: 'pipe', diff --git a/components/README.md b/components/README.md new file mode 100644 index 0000000..a2670a8 --- /dev/null +++ b/components/README.md @@ -0,0 +1,61 @@ +# Global Components Library + +Reusable components for all modes and projects. + +## Purpose + +This folder contains battle-tested, production-ready components that are shared across Mode1Test, Mode2Test, and future modes. When a component proves solid, it gets promoted here for wider use. + +## Structure + +Each component lives in its own folder with: +- Component code (TypeScript) +- Styling (TailwindCSS utilities) +- Documentation (how to use, props, dependencies, adjustments) +- Examples (if complex) + +Example structure: +``` +components/ + JobCard/ + JobCard.ts # Component implementation + README.md # Usage documentation +``` + +## Using Components in a Mode + +Import from the parent directory: + +```typescript +import JobCard from '../../../components/JobCard/JobCard'; +``` + +Or configure path aliases in your mode's `tsconfig.json`: + +```json +{ + "compilerOptions": { + "paths": { + "@components/*": ["../components/*"] + } + } +} +``` + +Then import as: +```typescript +import JobCard from '@components/JobCard/JobCard'; +``` + +## Adding New Components + +1. Create a folder with the component name +2. Write TypeScript with full type safety +3. Use only TailwindCSS for styling +4. Document: what it does, how to use, what props/env vars needed +5. Test in a mode thoroughly +6. When proven, commit with clear description + +## Components + +(None yet—first proven component will be added here)