From 894046427f2dad3d6fac3ab375963f6945aaacc6 Mon Sep 17 00:00:00 2001 From: Ashley Herrera Date: Sun, 25 Jan 2026 20:21:26 -0500 Subject: [PATCH 1/3] Removing unnecessary console logs --- src/api/middlewares/ErrorHandler.ts | 2 +- src/app.ts | 7 ------- src/services/NotifService.ts | 11 +++-------- src/services/PostService.ts | 11 ----------- 4 files changed, 4 insertions(+), 27 deletions(-) diff --git a/src/api/middlewares/ErrorHandler.ts b/src/api/middlewares/ErrorHandler.ts index 3f55f96..1dfea8f 100644 --- a/src/api/middlewares/ErrorHandler.ts +++ b/src/api/middlewares/ErrorHandler.ts @@ -20,7 +20,7 @@ function handleError(error: Error, error: message, httpCode: httpCode, }; - console.log(message); + console.error(`[${httpCode}] ${message}`); response.status(httpCode).json(errorResponse); next(); } \ No newline at end of file diff --git a/src/app.ts b/src/app.ts index af58b8b..d42172b 100644 --- a/src/app.ts +++ b/src/app.ts @@ -20,10 +20,6 @@ import * as admin from "firebase-admin"; dotenv.config(); -console.log( - "FIREBASE_SERVICE_ACCOUNT_PATH:", - process.env.FIREBASE_SERVICE_ACCOUNT_PATH, -); var serviceAccountPath = process.env.FIREBASE_SERVICE_ACCOUNT_PATH!; const serviceAccount = require(serviceAccountPath); @@ -78,7 +74,6 @@ async function main() { controllers: controllers, middlewares: middlewares, currentUserChecker: async (action: any) => { - console.log("AUTH MIDDLEWARE CALLED for path:", action.request.path); const authHeader = action.request.headers["authorization"]; if (!authHeader) { throw new ForbiddenError("No authorization token provided"); @@ -94,9 +89,7 @@ async function main() { const email = decodedToken.email; const userId = decodedToken.uid; action.request.email = email; - console.log("uid"); action.request.firebaseUid = userId; - console.log("here"); if (!email || !email.endsWith("@cornell.edu")) { throw new ForbiddenError("Only Cornell email addresses are allowed"); } diff --git a/src/services/NotifService.ts b/src/services/NotifService.ts index f95e58e..b3920b3 100644 --- a/src/services/NotifService.ts +++ b/src/services/NotifService.ts @@ -21,7 +21,6 @@ export class NotifService { public sendFCMNotifs = async (notifs: NotificationData[], userId: string) => { return this.transactions.readWrite(async (transactionalEntityManager) => { const notifRepository = Repositories.notification(transactionalEntityManager); - console.log(notifs); for (let notif of notifs) { const msg: Message = { notification: { @@ -73,8 +72,6 @@ export class NotifService { allDeviceTokens.push(token); } - console.log(allDeviceTokens); - let notif: NotificationData = { to: allDeviceTokens.map(tokenObj => tokenObj.token), sound: 'default', @@ -100,7 +97,7 @@ export class NotifService { httpCode: 200 }; } catch (err) { - console.log(err) + console.error('Failed to send notification:', err); return { message: "Notification not sent", httpCode: 500 @@ -132,8 +129,6 @@ export class NotifService { allDeviceTokens.push(token); } - console.log(allDeviceTokens); - let notif: NotificationData = { to: allDeviceTokens.map(tokenObj => tokenObj.token), sound: 'default', @@ -166,7 +161,7 @@ export class NotifService { httpCode: 200 }; } catch (err) { - console.log(err); + console.error('Failed to send discount notification:', err); return { message: "Notification not sent", httpCode: 500 @@ -238,7 +233,7 @@ export class NotifService { httpCode: 200 }; } catch (err) { - console.log(err); + console.error('Failed to send request match notification:', err); return { message: "Notification not sent", httpCode: 500 diff --git a/src/services/PostService.ts b/src/services/PostService.ts index 2d92fbc..c74b4a6 100644 --- a/src/services/PostService.ts +++ b/src/services/PostService.ts @@ -57,17 +57,6 @@ export class PostService { } public async createPost(post: CreatePostRequest, authenticatedUser: UserModel): Promise { - console.log('=== CREATE POST DEBUG ==='); - console.log('authenticatedUser:', authenticatedUser); - console.log('authenticatedUser type:', typeof authenticatedUser); - console.log('authenticatedUser === null:', authenticatedUser === null); - console.log('authenticatedUser === undefined:', authenticatedUser === undefined); - if (authenticatedUser) { - console.log('user.firebaseUid:', authenticatedUser.firebaseUid); - console.log('user.isActive:', authenticatedUser.isActive); - } - console.log('========================'); - return this.transactions.readWrite(async (transactionalEntityManager) => { const user = authenticatedUser; if (!user) throw new NotFoundError('User is null or undefined!'); From 565597bdf476bb7bb9b76f4684a7f492609c0f49 Mon Sep 17 00:00:00 2001 From: Ashley Herrera Date: Sun, 25 Jan 2026 20:40:05 -0500 Subject: [PATCH 2/3] Using precomputed embeddings for ML features --- src/services/PostService.ts | 25 +++++++++++++------------ src/utils/SentenceEncoder.ts | 4 ++-- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/src/services/PostService.ts b/src/services/PostService.ts index c74b4a6..2ad7a3c 100644 --- a/src/services/PostService.ts +++ b/src/services/PostService.ts @@ -405,12 +405,12 @@ export class PostService { const postRepository = Repositories.post(transactionalEntityManager); const post = await postRepository.getPostById(params.id); if (!post) throw new NotFoundError('Post not found!'); - const embedding = post.embedding - if (embedding == null) { - // TODO: after writing migration, throw new NotFoundError('Post does not have embedding!'); + + // Posts with no embeddings cant have similar posts, so return empty array + if (post.embedding == null) { return []; } - const similarPosts = await postRepository.getSimilarPosts(embedding, post.id, user.firebaseUid); + const similarPosts = await postRepository.getSimilarPosts(post.embedding, post.id, user.firebaseUid); const activePosts = this.filterInactiveUserPosts(similarPosts); return this.filterBlockedUserPosts(activePosts, user); }); @@ -484,18 +484,19 @@ export class PostService { // Get the search by ID const search = await searchRepository.getSearchById(searchIndex); if (!search) throw new NotFoundError('Search not found!'); - // Parse vector + // Parse search vector const searchVector: number[] = JSON.parse(search.searchVector); // Get active, unarchived posts const allPosts = await postRepository.getAllPosts(); - const model = await getLoadedModel(); - // For each post, generate embedding from the title - const postEmbeddings = await model.embed(allPosts.map(p => p.title)); - const embeddingsArray = postEmbeddings.arraySync(); - // Find similarity - const scoredPosts = allPosts.map((post, idx) => ({ + const postsWithEmbeddings = allPosts.filter(p => p.embedding != null); + + if (postsWithEmbeddings.length === 0) { + return []; + } + // Use precomputed embeddings from db + const scoredPosts = postsWithEmbeddings.map(post => ({ id: post.id, - similarity: this.similarity(searchVector, embeddingsArray[idx]) + similarity: this.similarity(searchVector, post.embedding as number[]) })); // Sort by similarity (descending order) and choose top N const topPosts = scoredPosts.sort((a, b) => b.similarity - a.similarity).slice(0, postCount); diff --git a/src/utils/SentenceEncoder.ts b/src/utils/SentenceEncoder.ts index 63b61dd..14d7538 100644 --- a/src/utils/SentenceEncoder.ts +++ b/src/utils/SentenceEncoder.ts @@ -1,5 +1,5 @@ -require("@tensorflow/tfjs-node"); -const use = require("@tensorflow-models/universal-sentence-encoder"); +import * as tf from "@tensorflow/tfjs-node"; +import * as use from "@tensorflow-models/universal-sentence-encoder"; let loadedModel: any; From a20fa94ea111b8a5ad1b3260214de8e10ad2b4bb Mon Sep 17 00:00:00 2001 From: Ashley Herrera Date: Sun, 25 Jan 2026 20:50:57 -0500 Subject: [PATCH 3/3] Consolidating duplicate notification logic --- src/services/NotifService.ts | 269 ++++++++++++++--------------------- 1 file changed, 104 insertions(+), 165 deletions(-) diff --git a/src/services/NotifService.ts b/src/services/NotifService.ts index b3920b3..72cb982 100644 --- a/src/services/NotifService.ts +++ b/src/services/NotifService.ts @@ -1,14 +1,21 @@ -import { NotFoundError} from 'routing-controllers'; +import { NotFoundError } from 'routing-controllers'; import { Service } from 'typedi'; -import { NotificationData, FindTokensRequest, DiscountNotificationRequest, RequestMatchNotificationRequest, TokenWrapper } from '../types'; +import { NotificationData, FindTokensRequest, DiscountNotificationRequest, RequestMatchNotificationRequest } from '../types'; import Repositories, { TransactionsManager } from '../repositories'; import { EntityManager } from 'typeorm'; import { InjectManager } from 'typeorm-typedi-extensions'; -// var accessToken = process.env['EXPO_ACCESS_TOKEN'] -// const expoServer = new Expo({ accessToken: accessToken }); -// import * as admin from 'firebase-admin'; import { getMessaging, Message } from 'firebase-admin/messaging'; -import { admin } from '../app'; + +interface NotifPayload { + title: string; + body: string; + data: Record; +} + +interface NotifResult { + message: string; + httpCode: number; +} @Service() export class NotifService { @@ -18,227 +25,159 @@ export class NotifService { this.transactions = new TransactionsManager(entityManager); } - public sendFCMNotifs = async (notifs: NotificationData[], userId: string) => { - return this.transactions.readWrite(async (transactionalEntityManager) => { - const notifRepository = Repositories.notification(transactionalEntityManager); - for (let notif of notifs) { - const msg: Message = { - notification: { - title: notif.title, - body: notif.body, - }, - data: notif.data as unknown as { [key: string]: string }, - token: notif.to[0], - }; - try { - // Send FCM notification - const response = await getMessaging().send(msg); - console.log(`Notification sent successfully: ${response}`); - - // Save notification to database - await notifRepository.save({ - userId: userId, - title: notif.title, - body: notif.body, - data: notif.data, - read: false - }); - } catch (error) { - console.warn( - `Skipping invalid token for notification "${notif.title}": ${error.message}` - ); - // Do NOT throw, just skip - continue; - } - } - }); + /** + * fetch all FCM tokens for a user + */ + private async getTokensForUser( + fcmTokenRepository: ReturnType, + userId: string + ): Promise { + const tokenRecords = await fcmTokenRepository.getTokensByUserId(userId); + return tokenRecords.map(t => t.token); } - + /** + * send notification to a user and persist it, skips invalid tokens + */ + private async sendToUser( + userId: string, + tokens: string[], + payload: NotifPayload, + notifRepository: ReturnType + ): Promise { + for (const token of tokens) { + const msg: Message = { + notification: { + title: payload.title, + body: payload.body, + }, + data: payload.data as { [key: string]: string }, + token: token, + }; + try { + await getMessaging().send(msg); + await notifRepository.save({ + userId: userId, + title: payload.title, + body: payload.body, + data: payload.data as unknown as JSON, + read: false + }); + } catch (error) { + console.warn(`Skipping invalid token for notification "${payload.title}": ${error.message}`); + continue; + } + } + } + /** + * send a notification to a user by userId + * gets tokens, sends, persists, and returns result + */ + private async sendNotificationToUser( + transactionalEntityManager: EntityManager, + userId: string, + payload: NotifPayload + ): Promise { + const fcmTokenRepository = Repositories.fcmToken(transactionalEntityManager); + const notifRepository = Repositories.notification(transactionalEntityManager); + + const tokens = await this.getTokensForUser(fcmTokenRepository, userId); + if (tokens.length === 0) { + return { message: "No device tokens found", httpCode: 200 }; + } + + try { + await this.sendToUser(userId, tokens, payload, notifRepository); + return { message: "Notification sent successfully", httpCode: 200 }; + } catch (err) { + console.error('Failed to send notification:', err); + return { message: "Notification not sent", httpCode: 500 }; + } + } - public async sendNotifs(request: FindTokensRequest) { + public async sendNotifs(request: FindTokensRequest): Promise { return this.transactions.readWrite(async (transactionalEntityManager) => { const userRepository = Repositories.user(transactionalEntityManager); - const fcmTokenRepository = Repositories.fcmToken(transactionalEntityManager); - let user = await userRepository.getUserByEmail(request.email); + const user = await userRepository.getUserByEmail(request.email); if (!user) { throw new NotFoundError("User not found!"); } - const allDeviceTokens = []; - const alltokens = await fcmTokenRepository.getTokensByUserId(user.firebaseUid); - for (var token of alltokens) { - - allDeviceTokens.push(token); - - } - let notif: NotificationData = { - to: allDeviceTokens.map(tokenObj => tokenObj.token), - sound: 'default', + + return this.sendNotificationToUser(transactionalEntityManager, user.firebaseUid, { title: request.title, body: request.body, - data: request.data as unknown as JSON - - } - try { - let notifs: NotificationData[] = []; - notif.to.forEach(token => { - notifs.push({ - to: [token], - sound: notif.sound, - title: notif.title, - body: notif.body, - data: notif.data - }) - }) - await this.sendFCMNotifs(notifs, user.firebaseUid) - return { - message: "Notification sent successfully", - httpCode: 200 - }; - } catch (err) { - console.error('Failed to send notification:', err); - return { - message: "Notification not sent", - httpCode: 500 - }; - } - }) + data: request.data as Record + }); + }); } - public async sendDiscountNotification(request: DiscountNotificationRequest) { + public async sendDiscountNotification(request: DiscountNotificationRequest): Promise { return this.transactions.readWrite(async (transactionalEntityManager) => { const userRepository = Repositories.user(transactionalEntityManager); - const fcmTokenRepository = Repositories.fcmToken(transactionalEntityManager); const postRepository = Repositories.post(transactionalEntityManager); - let user = await userRepository.getUserById(request.sellerId); + const user = await userRepository.getUserById(request.sellerId); if (!user) { throw new NotFoundError("User not found!"); } - let post = await postRepository.getPostById(request.listingId); + const post = await postRepository.getPostById(request.listingId); if (!post) { throw new NotFoundError("Post not found!"); } - const allDeviceTokens = []; - const allTokens = await fcmTokenRepository.getTokensByUserId(user.firebaseUid); - for (var token of allTokens) { - - allDeviceTokens.push(token); - - } - let notif: NotificationData = { - to: allDeviceTokens.map(tokenObj => tokenObj.token), - sound: 'default', + return this.sendNotificationToUser(transactionalEntityManager, user.firebaseUid, { title: "Price Drop Alert!", body: `${post.title} is now available at $${request.newPrice}!`, data: { postId: post.id, postTitle: post.title, - originalPrice: request.oldPrice, - newPrice: request.newPrice, + originalPrice: String(request.oldPrice), + newPrice: String(request.newPrice), sellerId: post.user.firebaseUid, sellerUsername: post.user.username - } as unknown as JSON - } - - try { - let notifs: NotificationData[] = []; - notif.to.forEach(token => { - notifs.push({ - to: [token], - sound: notif.sound, - title: notif.title, - body: notif.body, - data: notif.data - }); - }); - await this.sendFCMNotifs(notifs, user.firebaseUid); - return { - message: "Notification sent successfully", - httpCode: 200 - }; - } catch (err) { - console.error('Failed to send discount notification:', err); - return { - message: "Notification not sent", - httpCode: 500 - }; - } + } + }); }); } - public async sendRequestMatchNotification(request: RequestMatchNotificationRequest) { + public async sendRequestMatchNotification(request: RequestMatchNotificationRequest): Promise { return this.transactions.readWrite(async (transactionalEntityManager) => { const userRepository = Repositories.user(transactionalEntityManager); - const fcmTokenRepository = Repositories.fcmToken(transactionalEntityManager); const postRepository = Repositories.post(transactionalEntityManager); const requestRepository = Repositories.request(transactionalEntityManager); - let user = await userRepository.getUserById(request.userId); + const user = await userRepository.getUserById(request.userId); if (!user) { throw new NotFoundError("User not found!"); } - let post = await postRepository.getPostById(request.listingId); + const post = await postRepository.getPostById(request.listingId); if (!post) { throw new NotFoundError("Post not found!"); } - let userRequest = await requestRepository.getRequestById(request.requestId); + const userRequest = await requestRepository.getRequestById(request.requestId); if (!userRequest) { throw new NotFoundError("Request not found!"); } - const allDeviceTokens = []; - const allTokens = await fcmTokenRepository.getTokensByUserId(user.firebaseUid); - for (var token of allTokens) { - - allDeviceTokens.push(token); - - } + const price = post.altered_price > 0 ? post.altered_price : post.original_price; - let notif: NotificationData = { - to: allDeviceTokens.map(tokenObj => tokenObj.token), - sound: 'default', + return this.sendNotificationToUser(transactionalEntityManager, request.userId, { title: "Request Match Found!", body: `We found a match for your request: ${post.title}`, data: { postId: post.id, postTitle: post.title, - price: post.altered_price > 0 ? post.altered_price : post.original_price, + price: String(price), requestId: userRequest.id, requestTitle: userRequest.title, sellerId: post.user.firebaseUid, sellerUsername: post.user.username - } as unknown as JSON - } - - try { - let notifs: NotificationData[] = []; - notif.to.forEach(token => { - notifs.push({ - to: [token], - sound: notif.sound, - title: notif.title, - body: notif.body, - data: notif.data - }); - }); - await this.sendFCMNotifs(notifs, request.userId); - return { - message: "Notification sent successfully", - httpCode: 200 - }; - } catch (err) { - console.error('Failed to send request match notification:', err); - return { - message: "Notification not sent", - httpCode: 500 - }; - } + } + }); }); }