/opt/canhelp/apps/api/src/routes
Edit: /opt/canhelp/apps/api/src/routes/favorites.ts (4441B)
import { Hono } from 'hono'
import { eq, and, desc } from 'drizzle-orm'
import { db } from '../db.js'
import { favorites, tasks, specialistFavorites, users, plans } from '@canhelp/db'
import { requireAuth, type AuthVariables } from '../middleware/auth.js'
const app = new Hono<{ Variables: AuthVariables }>()
/** Check if user's plan includes favorites */
async function checkFavoritesAccess(userId: string): Promise
{
const [user] = await db.select({ planId: users.planId, role: users.role }).from(users).where(eq(users.id, userId))
if (user?.role === 'admin') return true
if (!user?.planId) return false
const [plan] = await db.select({ hasFavorites: plans.hasFavorites }).from(plans).where(eq(plans.id, user.planId))
return !!plan?.hasFavorites
}
// GET /favorites
app.get('/', requireAuth, async (c) => {
const user = c.get('user')
const rows = await db
.select({ favorite: favorites, task: tasks })
.from(favorites)
.leftJoin(tasks, eq(favorites.taskId, tasks.id))
.where(eq(favorites.userId, user.id))
.orderBy(desc(favorites.createdAt))
return c.json(rows)
})
// POST /favorites/:taskId
app.post('/:taskId', requireAuth, async (c) => {
const user = c.get('user')
const taskId = c.req.param('taskId')
const hasAccess = await checkFavoritesAccess(user.id)
if (!hasAccess) {
return c.json({ error: 'Your plan does not include favorites', code: 'PLAN_FEATURE_RESTRICTED' }, 403)
}
const [task] = await db.select().from(tasks).where(eq(tasks.id, taskId))
if (!task) return c.json({ error: 'Task not found' }, 404)
const [existing] = await db
.select()
.from(favorites)
.where(and(eq(favorites.userId, user.id), eq(favorites.taskId, taskId)))
if (existing) return c.json(existing)
const [fav] = await db
.insert(favorites)
.values({ userId: user.id, taskId })
.returning()
return c.json(fav, 201)
})
// DELETE /favorites/:taskId
app.delete('/:taskId', requireAuth, async (c) => {
const user = c.get('user')
await db
.delete(favorites)
.where(and(eq(favorites.userId, user.id), eq(favorites.taskId, c.req.param('taskId'))))
return c.json({ ok: true })
})
// ─── Specialist favorites ──────────────────────────────────────────────────
// GET /favorites/specialists
app.get('/specialists', requireAuth, async (c) => {
const user = c.get('user')
const rows = await db
.select({
id: specialistFavorites.id,
createdAt: specialistFavorites.createdAt,
specialist: {
id: users.id,
name: users.name,
firstName: users.firstName,
lastName: users.lastName,
image: users.image,
bio: users.bio,
},
})
.from(specialistFavorites)
.leftJoin(users, eq(specialistFavorites.specialistId, users.id))
.where(eq(specialistFavorites.userId, user.id))
.orderBy(desc(specialistFavorites.createdAt))
return c.json(rows)
})
// POST /favorites/specialists/:specialistId
app.post('/specialists/:specialistId', requireAuth, async (c) => {
const user = c.get('user')
const specialistId = c.req.param('specialistId')
if (specialistId === user.id) return c.json({ error: 'Cannot favorite yourself' }, 400)
const hasAccess = await checkFavoritesAccess(user.id)
if (!hasAccess) {
return c.json({ error: 'Your plan does not include favorites', code: 'PLAN_FEATURE_RESTRICTED' }, 403)
}
const [specialist] = await db.select({ id: users.id }).from(users).where(eq(users.id, specialistId))
if (!specialist) return c.json({ error: 'Specialist not found' }, 404)
const [existing] = await db
.select()
.from(specialistFavorites)
.where(and(eq(specialistFavorites.userId, user.id), eq(specialistFavorites.specialistId, specialistId)))
if (existing) return c.json(existing)
const [fav] = await db
.insert(specialistFavorites)
.values({ userId: user.id, specialistId })
.returning()
return c.json(fav, 201)
})
// DELETE /favorites/specialists/:specialistId
app.delete('/specialists/:specialistId', requireAuth, async (c) => {
const user = c.get('user')
await db
.delete(specialistFavorites)
.where(and(
eq(specialistFavorites.userId, user.id),
eq(specialistFavorites.specialistId, c.req.param('specialistId')),
))
return c.json({ ok: true })
})
export default app