import { router, protectedProcedure, publicProcedure } from '../trpc-index'; import jwt from 'jsonwebtoken'; import { eq, and } from 'drizzle-orm'; import { z } from 'zod'; import { db } from '../../db/db_index'; import { users, userDetails, userCreds, notifCreds, unloggedUserTokens } from '../../db/schema'; import { ApiError } from '../../lib/api-error'; import { jwtSecret } from 'src/lib/env-exporter'; import { generateSignedUrlFromS3Url } from '../../lib/s3-client'; interface AuthResponse { token: string; user: { id: number; name: string | null; email: string | null; mobile: string | null; profileImage?: string | null; bio?: string | null; dateOfBirth?: string | null; gender?: string | null; occupation?: string | null; }; } const generateToken = (userId: number): string => { const secret = jwtSecret; if (!secret) { throw new ApiError('JWT secret not configured', 500); } return jwt.sign({ userId }, secret, { expiresIn: '7d' }); }; export const userRouter = router({ getSelfData: protectedProcedure .query(async ({ ctx }) => { const userId = ctx.user.userId; if (!userId) { throw new ApiError('User not authenticated', 401); } const [user] = await db .select() .from(users) .where(eq(users.id, userId)) .limit(1); if (!user) { throw new ApiError('User not found', 404); } // Get user details for profile image const [userDetail] = await db .select() .from(userDetails) .where(eq(userDetails.userId, userId)) .limit(1); // Generate signed URL for profile image if it exists const profileImageSignedUrl = userDetail?.profileImage ? await generateSignedUrlFromS3Url(userDetail.profileImage) : null; const response: Omit = { user: { id: user.id, name: user.name, email: user.email, mobile: user.mobile, profileImage: profileImageSignedUrl, bio: userDetail?.bio || null, dateOfBirth: userDetail?.dateOfBirth || null, gender: userDetail?.gender || null, occupation: userDetail?.occupation || null, }, }; return { success: true, data: response, }; }), checkProfileComplete: protectedProcedure .query(async ({ ctx }) => { const userId = ctx.user.userId; if (!userId) { throw new ApiError('User not authenticated', 401); } const result = await db .select() .from(users) .leftJoin(userCreds, eq(users.id, userCreds.userId)) .where(eq(users.id, userId)) .limit(1); if (result.length === 0) { throw new ApiError('User not found', 404); } const { users: user, user_creds: creds } = result[0]; return { isComplete: !!(user.name && user.email && creds), }; }), savePushToken: publicProcedure .input(z.object({ token: z.string() })) .mutation(async ({ input, ctx }) => { const { token } = input; const userId = ctx.user?.userId; if (userId) { // AUTHENTICATED USER // Check if token exists in notif_creds for this user const existing = await db.query.notifCreds.findFirst({ where: and( eq(notifCreds.userId, userId), eq(notifCreds.token, token) ), }); if (existing) { // Update lastVerified timestamp await db .update(notifCreds) .set({ lastVerified: new Date() }) .where(eq(notifCreds.id, existing.id)); } else { // Insert new token into notif_creds await db.insert(notifCreds).values({ userId, token, lastVerified: new Date(), }); } // Remove from unlogged_user_tokens if it exists await db .delete(unloggedUserTokens) .where(eq(unloggedUserTokens.token, token)); } else { // UNAUTHENTICATED USER // Save/update in unlogged_user_tokens const existing = await db.query.unloggedUserTokens.findFirst({ where: eq(unloggedUserTokens.token, token), }); if (existing) { await db .update(unloggedUserTokens) .set({ lastVerified: new Date() }) .where(eq(unloggedUserTokens.id, existing.id)); } else { await db.insert(unloggedUserTokens).values({ token, lastVerified: new Date(), }); } } return { success: true }; }), });