From 14e783f111e0dbc3f405a0e19afb6bde465023e0 Mon Sep 17 00:00:00 2001 From: Daniel Volz Date: Mon, 16 Mar 2026 21:21:41 +0100 Subject: [PATCH] fix: exclude obsolete medications from share flows --- backend/src/routes/doses.ts | 29 ++++++++++++++--------------- backend/src/routes/share.ts | 36 ++++++++++++++++++++---------------- 2 files changed, 34 insertions(+), 31 deletions(-) diff --git a/backend/src/routes/doses.ts b/backend/src/routes/doses.ts index 8f4d16a..f214a8e 100644 --- a/backend/src/routes/doses.ts +++ b/backend/src/routes/doses.ts @@ -61,11 +61,6 @@ const doseReadResponseSchema = { }, } as const; -function maskToken(token: string): string { - if (token.length <= 8) return token; - return `${token.slice(0, 4)}...${token.slice(-4)}`; -} - // Helper to get user ID from request // Returns anonymous user ID when auth is disabled async function getUserId(request: FastifyRequest, reply: FastifyReply): Promise { @@ -545,7 +540,7 @@ export async function doseRoutes(app: FastifyInstance) { const { share, reason } = await getActiveShareToken(token); if (!share) { - request.log.warn(`[ShareDose] Rejected read for token ${maskToken(token)} (reason=${reason})`); + request.log.warn(`[ShareDose] Rejected read: token=${token}, reason=${reason}`); return reply.notFound("Share link not found"); } @@ -603,14 +598,14 @@ export async function doseRoutes(app: FastifyInstance) { const { share, reason } = await getActiveShareToken(token); if (!share) { - request.log.warn(`[ShareDose] Rejected mark for token ${maskToken(token)} (reason=${reason})`); + request.log.warn(`[ShareDose] Rejected mark: token=${token}, doseId=${doseId}, reason=${reason}`); return reply.notFound("Share link not found"); } const isValidShareDoseId = await validateShareDoseId(share, doseId); if (!isValidShareDoseId) { request.log.warn( - `[ShareDose] Rejected invalid doseId in mark request (owner=${share.userId}, takenBy=${share.takenBy}, doseId=${doseId})` + `[ShareDose] Rejected invalid doseId in mark request: token=${token}, ownerUserId=${share.userId}, takenBy=${share.takenBy}, doseId=${doseId}` ); return reply.status(400).send({ error: "Invalid or unauthorized doseId" }); } @@ -622,7 +617,9 @@ export async function doseRoutes(app: FastifyInstance) { .where(and(eq(doseTracking.userId, share.userId), eq(doseTracking.doseId, doseId))); if (existing) { - request.log.debug(`[ShareDose] Duplicate mark ignored (owner=${share.userId}, doseId=${doseId})`); + request.log.debug( + `[ShareDose] Duplicate mark ignored: token=${token}, ownerUserId=${share.userId}, takenBy=${share.takenBy}, doseId=${doseId}` + ); return { success: true, message: "Already marked" }; } @@ -634,7 +631,7 @@ export async function doseRoutes(app: FastifyInstance) { }); if (outOfStock) { request.log.info( - `[ShareDose] Rejected out-of-stock mark request (owner=${share.userId}, takenBy=${share.takenBy}, doseId=${doseId})` + `[ShareDose] Rejected out-of-stock mark request: token=${token}, ownerUserId=${share.userId}, takenBy=${share.takenBy}, doseId=${doseId}` ); return reply.status(409).send({ error: "Medication is out of stock", code: "OUT_OF_STOCK" }); } @@ -651,7 +648,7 @@ export async function doseRoutes(app: FastifyInstance) { }); request.log.info( - `[ShareDose] Dose marked via share link (owner=${share.userId}, takenBy=${share.takenBy}, doseId=${doseId})` + `[ShareDose] Dose marked via share link: token=${token}, ownerUserId=${share.userId}, shareTakenBy=${share.takenBy}, markedBy=${markedBy}, doseId=${doseId}` ); return { success: true }; @@ -685,14 +682,14 @@ export async function doseRoutes(app: FastifyInstance) { const { share, reason } = await getActiveShareToken(token); if (!share) { - request.log.warn(`[ShareDose] Rejected unmark for token ${maskToken(token)} (reason=${reason})`); + request.log.warn(`[ShareDose] Rejected unmark: token=${token}, doseId=${doseId}, reason=${reason}`); return reply.notFound("Share link not found"); } const isValidShareDoseId = await validateShareDoseId(share, doseId); if (!isValidShareDoseId) { request.log.warn( - `[ShareDose] Rejected invalid doseId in unmark request (owner=${share.userId}, takenBy=${share.takenBy}, doseId=${doseId})` + `[ShareDose] Rejected invalid doseId in unmark request: token=${token}, ownerUserId=${share.userId}, takenBy=${share.takenBy}, doseId=${doseId}` ); return reply.status(400).send({ error: "Invalid or unauthorized doseId" }); } @@ -705,14 +702,16 @@ export async function doseRoutes(app: FastifyInstance) { if (existing?.dismissed) { // Already dismissed - keep the record as-is - request.log.debug(`[ShareDose] Unmark ignored for dismissed dose (owner=${share.userId}, doseId=${doseId})`); + request.log.debug( + `[ShareDose] Unmark ignored for dismissed dose: token=${token}, ownerUserId=${share.userId}, takenBy=${share.takenBy}, doseId=${doseId}` + ); } else { // Not dismissed - delete the record entirely await db .delete(doseTracking) .where(and(eq(doseTracking.userId, share.userId), eq(doseTracking.doseId, doseId))); request.log.info( - `[ShareDose] Dose unmarked via share link (owner=${share.userId}, takenBy=${share.takenBy}, doseId=${doseId})` + `[ShareDose] Dose unmarked via share link: token=${token}, ownerUserId=${share.userId}, takenBy=${share.takenBy}, doseId=${doseId}` ); } diff --git a/backend/src/routes/share.ts b/backend/src/routes/share.ts index a50e415..b27ea43 100644 --- a/backend/src/routes/share.ts +++ b/backend/src/routes/share.ts @@ -96,11 +96,6 @@ const shareOverviewResponseSchema = { }, } as const; -function maskToken(token: string): string { - if (token.length <= 8) return token; - return `${token.slice(0, 4)}...${token.slice(-4)}`; -} - // Helper to get user ID from request // Returns anonymous user ID when auth is disabled async function getUserId(request: FastifyRequest, reply: FastifyReply): Promise { @@ -155,7 +150,7 @@ export async function shareRoutes(app: FastifyInstance) { // Find share token const [share] = await db.select().from(shareTokens).where(eq(shareTokens.token, token)); if (!share) { - request.log.warn(`[Share] Invalid share token requested: ${maskToken(token)}`); + request.log.warn(`[Share] Invalid share token requested: token=${token}`); return reply.status(404).send({ error: "Share link not found", code: "NOT_FOUND", @@ -165,7 +160,7 @@ export async function shareRoutes(app: FastifyInstance) { // Check if token has expired if (share.expiresAt && share.expiresAt.getTime() < Date.now()) { request.log.warn( - `[Share] Expired token requested: ${maskToken(token)} (owner=${share.userId}, takenBy=${share.takenBy})` + `[Share] Expired token requested: token=${token}, ownerUserId=${share.userId}, takenBy=${share.takenBy}` ); // Get the username of the owner to show in the expired message const [owner] = await db.select({ username: users.username }).from(users).where(eq(users.id, share.userId)); @@ -186,7 +181,10 @@ export async function shareRoutes(app: FastifyInstance) { // Get medications for this user filtered by takenBy (search in JSON array) // Use SQLite JSON function to check if takenBy is in the array - const allMeds = await db.select().from(medications).where(eq(medications.userId, share.userId)); + const allMeds = await db + .select() + .from(medications) + .where(and(eq(medications.userId, share.userId), eq(medications.isObsolete, false))); // Filter medications where takenBy matches either medication-level OR any intake-level takenBy const meds = allMeds.filter((med) => { @@ -301,19 +299,19 @@ export async function shareRoutes(app: FastifyInstance) { const { token } = request.params; if (!shareTokenPattern.test(token)) { - request.log.warn(`[ShareOverview] Rejected invalid token format: ${maskToken(token)}`); + request.log.warn(`[ShareOverview] Rejected invalid token format: token=${token}`); return reply.status(404).send({ error: "not_found" }); } const [share] = await db.select().from(shareTokens).where(eq(shareTokens.token, token)); if (!share) { - request.log.warn(`[ShareOverview] Unknown token requested: ${maskToken(token)}`); + request.log.warn(`[ShareOverview] Unknown token requested: token=${token}`); return reply.status(404).send({ error: "not_found" }); } if (share.expiresAt && share.expiresAt.getTime() < Date.now()) { request.log.warn( - `[ShareOverview] Expired token requested: ${maskToken(token)} (owner=${share.userId}, takenBy=${share.takenBy})` + `[ShareOverview] Expired token requested: token=${token}, ownerUserId=${share.userId}, takenBy=${share.takenBy}` ); return reply.status(410).send({ error: "expired", @@ -324,7 +322,10 @@ export async function shareRoutes(app: FastifyInstance) { const [settings] = await db.select().from(userSettings).where(eq(userSettings.userId, share.userId)); const [owner] = await db.select({ username: users.username }).from(users).where(eq(users.id, share.userId)); - const allMeds = await db.select().from(medications).where(eq(medications.userId, share.userId)); + const allMeds = await db + .select() + .from(medications) + .where(and(eq(medications.userId, share.userId), eq(medications.isObsolete, false))); const meds = allMeds.filter((med) => { const takenByArray = parseTakenByJson(med.takenByJson); const intakes = parseIntakesJson( @@ -392,7 +393,10 @@ export async function shareRoutes(app: FastifyInstance) { const { takenBy, scheduleDays } = parsed.data; // Check if user has medications for this takenBy (search in both medication-level and intake-level) - const allMeds = await db.select().from(medications).where(eq(medications.userId, userId)); + const allMeds = await db + .select() + .from(medications) + .where(and(eq(medications.userId, userId), eq(medications.isObsolete, false))); const medsForPerson = allMeds.filter((med) => { const takenByArray = parseTakenByJson(med.takenByJson); const intakes = parseIntakesJson( @@ -421,7 +425,7 @@ export async function shareRoutes(app: FastifyInstance) { await db.update(shareTokens).set({ scheduleDays, expiresAt: null }).where(eq(shareTokens.id, existingShare.id)); request.log.info( - `[Share] Reused existing share token (owner=${userId}, takenBy=${takenBy}, scheduleDays=${scheduleDays})` + `[Share] Reused existing share token: token=${existingShare.token}, ownerUserId=${userId}, takenBy=${takenBy}, scheduleDays=${scheduleDays}` ); return { @@ -443,7 +447,7 @@ export async function shareRoutes(app: FastifyInstance) { }); request.log.info( - `[Share] Created new share token (owner=${userId}, takenBy=${takenBy}, scheduleDays=${scheduleDays})` + `[Share] Created new share token: token=${token}, ownerUserId=${userId}, takenBy=${takenBy}, scheduleDays=${scheduleDays}` ); return { @@ -490,7 +494,7 @@ export async function shareRoutes(app: FastifyInstance) { intakeRemindersEnabled: medications.intakeRemindersEnabled, }) .from(medications) - .where(eq(medications.userId, userId)); + .where(and(eq(medications.userId, userId), eq(medications.isObsolete, false))); // Collect all unique person names from medication-level AND intake-level takenBy const allPeople = new Set();