/opt/canhelp/apps/api/src/routes
Edit: /opt/canhelp/apps/api/src/routes/dashboard.ts (5142B)
import { Hono } from 'hono'
import { eq, count, and, gte } from 'drizzle-orm'
import { db } from '../db.js'
import { users, plans, tasks, offers, reviews, specialistCards, portfolioItems, notifications, settings as settingsTable } from '@canhelp/db'
import { requireAuth, type AuthVariables } from '../middleware/auth.js'
import { getDailyUsageSummary } from '../lib/daily-limits.js'
import { getCalendarStatus } from '../lib/google-oauth.js'
import { getHelperCapability } from '../lib/helper-capability.js'
const app = new Hono<{ Variables: AuthVariables }>()
// GET /dashboard/me — authenticated user's plan dashboard
app.get('/me', requireAuth, async (c) => {
const authUser = c.get('user')
// Fetch user + plan
const [user] = await db.select().from(users).where(eq(users.id, authUser.id))
if (!user) return c.json({ error: 'User not found' }, 404)
let plan = null
if (user.planId) {
const [p] = await db.select().from(plans).where(eq(plans.id, user.planId))
plan = p ?? null
}
// Daily usage
const usage = await getDailyUsageSummary(authUser.id, plan)
// Stats
const isHelperReady = await getHelperCapability(user.id)
let stats: Record
= {}
const [unreadNotifications] = await db
.select({ total: count() })
.from(notifications)
.where(and(eq(notifications.recipientId, user.id), eq(notifications.isRead, false)))
if (isHelperReady) {
// Specialist stats: cards, portfolio, active offers, reviews
const [cardCount] = await db
.select({ total: count() })
.from(specialistCards)
.where(and(eq(specialistCards.specialistId, user.id), eq(specialistCards.isActive, true)))
const [totalCardCount] = await db
.select({ total: count() })
.from(specialistCards)
.where(eq(specialistCards.specialistId, user.id))
const [portfolioCount] = await db
.select({ total: count() })
.from(portfolioItems)
.where(eq(portfolioItems.specialistId, user.id))
const [offerCount] = await db
.select({ total: count() })
.from(offers)
.where(and(eq(offers.specialistId, user.id), eq(offers.status, 'pending')))
const [reviewCount] = await db
.select({ total: count() })
.from(reviews)
.where(eq(reviews.targetId, user.id))
stats = {
cards: cardCount?.total ?? 0,
totalCards: totalCardCount?.total ?? 0,
maxCards: plan?.maxCards ?? 1,
portfolioItems: portfolioCount?.total ?? 0,
maxPortfolioItems: plan?.maxPortfolioItems ?? 5,
activeOffers: offerCount?.total ?? 0,
reviewsReceived: reviewCount?.total ?? 0,
unreadNotifications: unreadNotifications?.total ?? 0,
}
} else {
// Customer stats: active tasks, completed tasks
const [activeTaskCount] = await db
.select({ total: count() })
.from(tasks)
.where(and(eq(tasks.customerId, user.id), eq(tasks.status, 'open')))
const [completedTaskCount] = await db
.select({ total: count() })
.from(tasks)
.where(and(eq(tasks.customerId, user.id), eq(tasks.status, 'completed')))
stats = {
activeTasks: activeTaskCount?.total ?? 0,
completedTasks: completedTaskCount?.total ?? 0,
unreadNotifications: unreadNotifications?.total ?? 0,
}
}
// Google Calendar status (for specialists)
let googleCalendar = null
if (isHelperReady && plan?.hasGoogleCalendar) {
try {
googleCalendar = await getCalendarStatus(user.id)
} catch {
googleCalendar = { connected: false }
}
}
// Plan expiration
const planExpiration = user.planExpiresAt ? {
expiresAt: user.planExpiresAt,
daysLeft: Math.max(0, Math.ceil((new Date(user.planExpiresAt).getTime() - Date.now()) / (1000 * 60 * 60 * 24))),
} : null
// Feature summary
const features = plan ? {
canContactFreePlan: plan.canContactFreePlan,
canContactProPlan: plan.canContactProPlan,
canContactAll: plan.canContactAll,
canShowContactInfo: plan.canShowContactInfo,
canViewPhone: plan.canViewPhone,
canUploadVideo: plan.canUploadVideo,
hasFavorites: plan.hasFavorites,
hasGoogleCalendar: plan.hasGoogleCalendar,
hasVerifiedBadge: plan.hasVerifiedBadge,
hasPersonalSite: plan.hasPersonalSite,
hasCanHelpNowStatus: plan.hasCanHelpNowStatus,
hasNeedHelpStatus: plan.hasNeedHelpStatus,
hasAutoResponse: plan.hasAutoResponse,
hasAutoMatch: plan.hasAutoMatch,
hasPriceList: plan.hasPriceList,
highlightedReviews: plan.highlightedReviews,
searchBoost: plan.searchBoost,
notifyNewTasks: plan.notifyNewTasks,
} : null
const [showPricingSetting] = await db
.select({ value: settingsTable.value })
.from(settingsTable)
.where(eq(settingsTable.key, 'general.showPricing'))
return c.json({
plan: plan ? {
id: plan.id,
name: plan.name,
role: plan.role,
tier: plan.tier,
price: plan.price,
} : null,
planExpiration,
usage,
stats,
features,
showPricing: showPricingSetting?.value !== 'false',
googleCalendar,
personalSiteSlug: user.personalSiteSlug,
})
})
export default app