freshyo/apps/backend/src/trpc/user-apis/user.ts
2026-02-28 00:26:05 +05:30

170 lines
No EOL
4.6 KiB
TypeScript

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<AuthResponse, 'token'> = {
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 };
}),
});