/opt/canhelp/apps/api/src/routes
NameSizeModeActions
admin.ts950570644editdlrm
auto-match.ts74060644editdlrm
auto-response.ts81110644editdlrm
availability.ts72940644editdlrm
categories.ts18670644editdlrm
chat.ts93770644editdlrm
dashboard.ts51420644editdlrm
favorites.ts44410644editdlrm
fomo.ts68440644editdlrm
google.ts48720644editdlrm
health.ts12140644editdlrm
i18n.ts1670860644editdlrm
locations.ts17810644editdlrm
notifications.ts28620644editdlrm
offers.ts100470644editdlrm
payments.ts211670644editdlrm
plans.ts13520644editdlrm
portfolio.ts66580644editdlrm
price-list.ts144170644editdlrm
reports.ts20070644editdlrm
reviews.ts27830644editdlrm
skill-suggestions.ts10350644editdlrm
specialist-cards.ts186410644editdlrm
stats.ts10930644editdlrm
statuses.ts37310644editdlrm
support.ts84520644editdlrm
tasks.ts300180644editdlrm
uploads.ts15260644editdlrm
users.ts371570644editdlrm
Edit: /opt/canhelp/apps/api/src/routes/google.ts (4872B)
/** * Google routes: * - Google Sign-In is handled natively by Better Auth (socialProviders.google) * via /api/auth/sign-in/social — no custom routes needed. * * - Google Calendar: custom OAuth flow to connect/disconnect Calendar * and push/import availability, stored in google_calendar_tokens table. */ import { Hono } from 'hono' import { eq } from 'drizzle-orm' import { requireAuth, type AuthVariables } from '../middleware/auth.js' import { db } from '../db.js' import { users, plans } from '@canhelp/db' import { getCalendarAuthUrl, exchangeCode, storeCalendarToken, getCalendarStatus, disconnectCalendar, importBusyTimes, runFirstImport, } from '../lib/google-oauth.js' const app = new Hono<{ Variables: AuthVariables }>() const WEB_URL = process.env.WEB_URL || 'http://localhost:3000' // ─── Calendar status ────────────────────────────────────────────────────── // GET /google/calendar/status → { connected, email?, googleUserId? } app.get('/calendar/status', requireAuth, async (c) => { const user = c.get('user') const status = await getCalendarStatus(user.id) return c.json(status) }) // ─── Calendar connect (OAuth start) ────────────────────────────────────── // GET /google/calendar/connect → redirects to Google consent screen app.get('/calendar/connect', requireAuth, async (c) => { const user = c.get('user') // Check plan allows Google Calendar const [dbUser] = await db.select({ planId: users.planId }).from(users).where(eq(users.id, user.id)) if (dbUser?.planId) { const [plan] = await db.select({ hasGoogleCalendar: plans.hasGoogleCalendar }).from(plans).where(eq(plans.id, dbUser.planId)) if (!plan?.hasGoogleCalendar) { return c.json({ error: 'Google Calendar requires a Pro or Ultimate plan', code: 'PLAN_UPGRADE_REQUIRED' }, 403) } } else { return c.json({ error: 'Google Calendar requires a Pro or Ultimate plan', code: 'PLAN_UPGRADE_REQUIRED' }, 403) } if (!process.env.GOOGLE_CLIENT_ID || !process.env.GOOGLE_CLIENT_SECRET) { return c.json({ error: 'Google OAuth is not configured on this server' }, 503) } const url = getCalendarAuthUrl(user.id) return c.redirect(url) }) // ─── Calendar OAuth callback ────────────────────────────────────────────── // GET /google/calendar/callback?code=...&state=userId app.get('/calendar/callback', async (c) => { const code = c.req.query('code') const userId = c.req.query('state') const error = c.req.query('error') if (error || !code || !userId) { return c.redirect(`${WEB_URL}/schedule?gcal=error`) } try { const { tokens, googleUserId, email } = await exchangeCode(code) if (!tokens.access_token) { return c.redirect(`${WEB_URL}/schedule?gcal=error`) } await storeCalendarToken(userId, tokens, googleUserId, email) // Auto-import busy times for today + 3 months (fire-and-forget) runFirstImport(userId).catch((err) => console.error('[gcal] first import error:', err)) return c.redirect(`${WEB_URL}/schedule?gcal=connected`) } catch (err) { console.error('[gcal] callback error:', err) return c.redirect(`${WEB_URL}/schedule?gcal=error`) } }) // ─── Calendar disconnect ────────────────────────────────────────────────── // DELETE /google/calendar/disconnect app.delete('/calendar/disconnect', requireAuth, async (c) => { const user = c.get('user') await disconnectCalendar(user.id) return c.json({ ok: true }) }) // ─── Manual import ──────────────────────────────────────────────────────── // POST /google/calendar/import // Import Google Calendar busy times for today + 3 months into specialist_availability app.post('/calendar/import', requireAuth, async (c) => { const user = c.get('user') const status = await getCalendarStatus(user.id) if (!status.connected) { return c.json({ error: 'Google Calendar not connected' }, 400) } try { const today = new Date().toISOString().slice(0, 10) const threeMonths = new Date() threeMonths.setMonth(threeMonths.getMonth() + 3) const to = threeMonths.toISOString().slice(0, 10) const count = await importBusyTimes(user.id, today, to) return c.json({ ok: true, imported: count }) } catch (err) { console.error('[gcal] import error:', err) return c.json({ error: 'Import failed' }, 500) } }) export default app