/opt/canhelp/apps/api/src/routes
Edit: /opt/canhelp/apps/api/src/routes/statuses.ts (3731B)
import { Hono } from 'hono'
import { zValidator } from '@hono/zod-validator'
import { z } from 'zod'
import { eq, and } from 'drizzle-orm'
import { db } from '../db.js'
import { userStatuses, users, plans } from '@canhelp/db'
import { requireAuth, type AuthVariables } from '../middleware/auth.js'
const app = new Hono<{ Variables: AuthVariables }>()
const STATUS_DURATION_HOURS = 8 // auto-expire after 8 hours
/** Check plan permission for a status type */
async function canUseStatus(userId: string, statusType: 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().from(plans).where(eq(plans.id, user.planId))
if (!plan) return false
if (statusType === 'canhelp_now') return !!plan.hasCanHelpNowStatus
if (statusType === 'need_help') return !!plan.hasNeedHelpStatus
return false
}
// GET /statuses/me — get current user's statuses
app.get('/me', requireAuth, async (c) => {
const user = c.get('user')
const rows = await db
.select()
.from(userStatuses)
.where(eq(userStatuses.userId, user.id))
// Auto-deactivate expired statuses
const now = new Date()
const result = rows.map((s) => {
if (s.isActive && s.expiresAt && s.expiresAt < now) {
return { ...s, isActive: false }
}
return s
})
return c.json(result)
})
// GET /statuses/user/:userId — get a user's active statuses (public)
app.get('/user/:userId', async (c) => {
const userId = c.req.param('userId')
const now = new Date()
const rows = await db
.select({
statusType: userStatuses.statusType,
isActive: userStatuses.isActive,
activatedAt: userStatuses.activatedAt,
expiresAt: userStatuses.expiresAt,
})
.from(userStatuses)
.where(and(eq(userStatuses.userId, userId), eq(userStatuses.isActive, true)))
// Filter out expired
const active = rows.filter((s) => !s.expiresAt || s.expiresAt > now)
return c.json(active)
})
// POST /statuses/toggle — activate or deactivate a status
app.post(
'/toggle',
requireAuth,
zValidator(
'json',
z.object({
statusType: z.enum(['canhelp_now', 'need_help']),
active: z.boolean(),
}),
),
async (c) => {
const user = c.get('user')
const { statusType, active } = c.req.valid('json')
// Check plan permission
if (active) {
const allowed = await canUseStatus(user.id, statusType)
if (!allowed) {
return c.json({
error: `Your plan does not include the "${statusType}" status`,
code: 'PLAN_FEATURE_RESTRICTED',
}, 403)
}
}
const now = new Date()
const expiresAt = active
? new Date(now.getTime() + STATUS_DURATION_HOURS * 60 * 60 * 1000)
: null
// Upsert: find existing or create
const [existing] = await db
.select()
.from(userStatuses)
.where(and(eq(userStatuses.userId, user.id), eq(userStatuses.statusType, statusType)))
if (existing) {
const [updated] = await db
.update(userStatuses)
.set({
isActive: active,
activatedAt: active ? now : existing.activatedAt,
expiresAt,
})
.where(eq(userStatuses.id, existing.id))
.returning()
return c.json(updated)
}
const [created] = await db
.insert(userStatuses)
.values({
userId: user.id,
statusType,
isActive: active,
activatedAt: active ? now : null,
expiresAt,
})
.returning()
return c.json(created, 201)
},
)
export default app