import { Hono } from 'hono'; import { serveStatic } from 'hono/bun'; import { cors } from 'hono/cors'; import PocketBase from 'pocketbase'; import { ConfidentialClientApplication } from '@azure/msal-node'; import { readFileSync } from 'fs'; import { join } from 'path'; // Load env from local .env const envPath = join(import.meta.dir, '.env'); try { const envContent = readFileSync(envPath, 'utf-8'); envContent.split('\n').forEach(line => { const trimmed = line.trim(); if (trimmed && !trimmed.startsWith('#')) { const [key, ...valueParts] = trimmed.split('='); if (key && valueParts.length > 0) { process.env[key.trim()] = valueParts.join('=').trim(); } } }); console.log('✓ Loaded env from .env'); } catch (e) { console.warn('⚠ Could not load .env:', (e as Error).message); } const app = new Hono(); // Enable CORS app.use('/*', cors({ origin: '*', credentials: true, })); // PocketBase client const pb = new PocketBase(process.env.PB_DB || 'http://127.0.0.1:8090'); // Use user's PocketBase token function setUserPocketBaseAuth(token: string) { pb.authStore.save(token, null); } // MSAL config for Graph token (client credentials) const msalConfig = { auth: { clientId: process.env.CLIENT_ID || '', authority: `https://login.microsoftonline.com/${process.env.TENANT_ID}`, clientSecret: process.env.CLIENT_SECRET || '', }, }; const cca = new ConfidentialClientApplication(msalConfig); async function getGraphToken(): Promise { const result = await cca.acquireTokenByClientCredential({ scopes: ['https://graph.microsoft.com/.default'], }); if (!result || !result.accessToken) { throw new Error('Failed to acquire Graph token'); } return result.accessToken; } // Health check app.get('/health', (c) => { return c.json({ ok: true, pbDB: process.env.PB_DB }); }); // Submit endpoint (example) app.post('/api/submit', async (c) => { try { const body = await c.req.json(); const pbToken = body.pbToken; if (!pbToken) { return c.json({ success: false, message: 'Missing pbToken' }, 401); } // Validate user token setUserPocketBaseAuth(pbToken); try { await pb.collection(process.env.PB_AUTH_COLLECTION || 'Users').authRefresh(); } catch (e) { console.error('auth_validation_failed', { message: (e as Error)?.message }); return c.json({ success: false, message: 'Invalid token' }, 401); } // Store in PocketBase collection (adjust fields as needed) const record = await pb .collection(process.env.PB_COLLECTION || 'Job_Info_TestEnv') .create({ ...body, submittedBy: pb.authStore.model?.email || 'unknown', submittedAt: new Date().toISOString(), }); console.log('submission_success', { recordId: record.id }); return c.json({ success: true, message: 'Feedback submitted successfully', recordId: record.id }); } catch (err) { console.error('submit_error', { message: (err as Error)?.message }); return c.json({ success: false, message: 'Internal error' }, 500); } }); // Serve static index.html app.get('/', async (c) => { const html = await Bun.file(new URL('./index.html', import.meta.url)).text(); return c.html(html); }); const PORT = Number(process.env.PORT || 5500); console.log(`PBandGraph server running at http://localhost:${PORT}`); export default { port: PORT, fetch: app.fetch, };