chahinebrini 89e4e3481b feat(calls): Phase 0 — calls_enabled opt-out + canCall guard (mutual-follow); DM UI batch
Backend (voice-call groundwork, no call engine yet):
- Profile.callsEnabled (Boolean default true) + migration
- canCall(caller,callee): mutual-follow AND callee.callsEnabled — server-side hard guard
- POST /api/me/calls-enabled (opt-out toggle), GET /api/chat/can-call/:userId
- expose callsEnabled in /api/auth/me

Frontend:
- "Allow calls" toggle in Profile privacy section (default on, optimistic+rollback)
- Me.callsEnabled + i18n DE/EN/FR/AR

Bundled DM UI work from this session:
- image lightbox is now a swipeable carousel over all shared images (+ counter)
- keyboard stays open after sending (input ref refocus)
- voice notes: Instagram-style waveforms (own=white/mint, other=black/grey),
  removed the blue progress dot; lazy-load expo-media-library with clean fallback
- expo-linear-gradient + expo-media-library deps

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 21:14:31 +02:00

45 lines
1.6 KiB
TypeScript

import { getProfile } from "../../db/profile";
import { getPlanLimits } from "../../utils/plan-features";
export default defineEventHandler(async (event) => {
const user = await requireUser(event);
const dbProfile = await getProfile(user.id);
const plan = (dbProfile?.plan === "premium"
? "legend"
: dbProfile?.plan === "standard"
? "pro"
: dbProfile?.plan ?? "free") as "free" | "pro" | "legend";
const limits = getPlanLimits(plan);
return {
id: user.id,
email: user.email,
username: dbProfile?.username ?? "",
nickname: dbProfile?.nickname ?? null,
avatar: dbProfile?.avatar ?? null,
plan,
foundingMember: dbProfile?.foundingMember ?? false,
streak: dbProfile?.streak ?? 0,
lyraVoiceId: dbProfile?.lyraVoiceId ?? null,
onboardingStep: dbProfile?.onboardingStep ?? "welcome",
created_at: dbProfile?.createdAt?.toISOString() ?? user.created_at,
// Für useUserPlan im Frontend — Key-Subset der PlanLimits
planLimits: {
customDomains: limits.customDomains, // { web: number, mail: number }
domainRefill: limits.domainRefill,
mailAgents: limits.mailAgents === Infinity ? null : limits.mailAgents,
globalBlocklist: limits.globalBlocklist,
maxAppDevices: limits.maxAppDevices,
maxProtectedDevices: limits.maxProtectedDevices,
canCreateGroup: limits.canCreateGroup,
canAddToBlocklist: limits.canAddToBlocklist,
},
globalBlocklistGraceUntil:
dbProfile?.globalBlocklistGraceUntil?.toISOString() ?? null,
presenceVisible: dbProfile?.presenceVisible ?? true,
callsEnabled: dbProfile?.callsEnabled ?? true,
};
});