Skip to content

Commit 0e748e5

Browse files
author
marcselman
committed
Store user dietary preferences in the database
Migrates dietary preference storage from in-memory to a PostgreSQL database using Drizzle ORM, defining a new schema and updating existing methods in `server/storage.ts` to interact with the database for setting and retrieving preferences. Also introduces `server/db/index.ts` for database connection and `server/db/schema.ts` for the new `dietary_preferences` table. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 9b278ad5-3a45-420e-ba83-0847baf9924f Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Event-Id: 93fa27d1-317a-4e5b-8d04-2278d8024c1d Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/04133160-d2cd-42c9-82f0-4e08d800b951/9b278ad5-3a45-420e-ba83-0847baf9924f/hPsu8WP Replit-Helium-Checkpoint-Created: true
1 parent 8146f41 commit 0e748e5

File tree

3 files changed

+101
-24
lines changed

3 files changed

+101
-24
lines changed

server/db/index.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { Pool } from "pg";
2+
import { drizzle } from "drizzle-orm/node-postgres";
3+
import * as schema from "./schema";
4+
5+
if (!process.env.DATABASE_URL) {
6+
throw new Error("DATABASE_URL environment variable is not set");
7+
}
8+
9+
const pool = new Pool({
10+
connectionString: process.env.DATABASE_URL,
11+
});
12+
13+
export const db = drizzle(pool, { schema });

server/db/schema.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { pgTable, text, serial, timestamp } from "drizzle-orm/pg-core";
2+
import { createInsertSchema } from "drizzle-zod";
3+
import { z } from "zod";
4+
5+
export const dietaryPreferences = pgTable("dietary_preferences", {
6+
id: serial("id").primaryKey(),
7+
sessionId: text("session_id").notNull(),
8+
email: text("email").notNull(),
9+
name: text("name").notNull(),
10+
preference: text("preference").notNull(),
11+
submittedAt: timestamp("submitted_at").defaultNow().notNull(),
12+
});
13+
14+
export const insertDietaryPreferenceSchema = createInsertSchema(dietaryPreferences).omit({
15+
id: true,
16+
submittedAt: true,
17+
});
18+
19+
export type InsertDietaryPreference = z.infer<typeof insertDietaryPreferenceSchema>;
20+
export type DietaryPreferenceRow = typeof dietaryPreferences.$inferSelect;

server/storage.ts

Lines changed: 68 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
import type { Session, ForumEdition, ForumData, DietaryPreference } from "@shared/schema";
22
import { getMicrosoftGraphService } from "./microsoft-graph";
3+
import { db } from "./db";
4+
import { dietaryPreferences } from "./db/schema";
5+
import { eq, and } from "drizzle-orm";
36

47
export interface IStorage {
58
getForumData(): Promise<ForumData>;
@@ -28,9 +31,6 @@ export class GraphStorage implements IStorage {
2831
private lastGraphAttempt: Date | null = null;
2932
private readonly MAX_FAILURES = 3;
3033
private readonly RETRY_DELAY_MS = 60000;
31-
32-
// In-memory storage for dietary preferences (sessionId -> email -> preference)
33-
private dietaryPreferences: Map<string, Map<string, DietaryPreference>> = new Map();
3434

3535
private shouldTryGraph(): boolean {
3636
if (this.graphFailures >= this.MAX_FAILURES) {
@@ -155,41 +155,85 @@ export class GraphStorage implements IStorage {
155155

156156
async setDietaryPreference(sessionId: string, email: string, name: string, preference: string): Promise<void> {
157157
const normalizedEmail = email.toLowerCase();
158-
if (!this.dietaryPreferences.has(sessionId)) {
159-
this.dietaryPreferences.set(sessionId, new Map());
160-
}
161-
const sessionPrefs = this.dietaryPreferences.get(sessionId)!;
158+
const trimmedPreference = preference.trim();
162159

163-
if (preference.trim() === "") {
164-
// Remove preference if empty
165-
sessionPrefs.delete(normalizedEmail);
160+
const existing = await db.select()
161+
.from(dietaryPreferences)
162+
.where(and(
163+
eq(dietaryPreferences.sessionId, sessionId),
164+
eq(dietaryPreferences.email, normalizedEmail)
165+
))
166+
.limit(1);
167+
168+
if (trimmedPreference === "") {
169+
if (existing.length > 0) {
170+
await db.delete(dietaryPreferences)
171+
.where(and(
172+
eq(dietaryPreferences.sessionId, sessionId),
173+
eq(dietaryPreferences.email, normalizedEmail)
174+
));
175+
}
176+
} else if (existing.length > 0) {
177+
await db.update(dietaryPreferences)
178+
.set({ name, preference: trimmedPreference, submittedAt: new Date() })
179+
.where(and(
180+
eq(dietaryPreferences.sessionId, sessionId),
181+
eq(dietaryPreferences.email, normalizedEmail)
182+
));
166183
} else {
167-
sessionPrefs.set(normalizedEmail, {
168-
email: normalizedEmail,
169-
name,
170-
preference: preference.trim(),
171-
submittedAt: new Date().toISOString(),
172-
});
184+
await db.insert(dietaryPreferences)
185+
.values({ sessionId, email: normalizedEmail, name, preference: trimmedPreference });
173186
}
174187
}
175188

176189
async getDietaryPreference(sessionId: string, email: string): Promise<DietaryPreference | undefined> {
177190
const normalizedEmail = email.toLowerCase();
178-
return this.dietaryPreferences.get(sessionId)?.get(normalizedEmail);
191+
const rows = await db.select()
192+
.from(dietaryPreferences)
193+
.where(and(
194+
eq(dietaryPreferences.sessionId, sessionId),
195+
eq(dietaryPreferences.email, normalizedEmail)
196+
))
197+
.limit(1);
198+
199+
if (rows.length === 0) return undefined;
200+
const row = rows[0];
201+
return {
202+
email: row.email,
203+
name: row.name,
204+
preference: row.preference,
205+
submittedAt: row.submittedAt.toISOString(),
206+
};
179207
}
180208

181209
async getDietaryPreferencesForSession(sessionId: string): Promise<DietaryPreference[]> {
182-
const sessionPrefs = this.dietaryPreferences.get(sessionId);
183-
if (!sessionPrefs) return [];
184-
return Array.from(sessionPrefs.values());
210+
const rows = await db.select()
211+
.from(dietaryPreferences)
212+
.where(eq(dietaryPreferences.sessionId, sessionId));
213+
214+
return rows.map(row => ({
215+
email: row.email,
216+
name: row.name,
217+
preference: row.preference,
218+
submittedAt: row.submittedAt.toISOString(),
219+
}));
185220
}
186221

187222
async getAllDietaryPreferences(): Promise<Map<string, DietaryPreference[]>> {
223+
const rows = await db.select().from(dietaryPreferences);
188224
const result = new Map<string, DietaryPreference[]>();
189-
const entries = Array.from(this.dietaryPreferences.entries());
190-
entries.forEach(([sessionId, prefs]) => {
191-
result.set(sessionId, Array.from(prefs.values()));
192-
});
225+
226+
for (const row of rows) {
227+
const prefs = result.get(row.sessionId) || [];
228+
prefs.push({
229+
email: row.email,
230+
name: row.name,
231+
preference: row.preference,
232+
submittedAt: row.submittedAt.toISOString(),
233+
});
234+
result.set(row.sessionId, prefs);
235+
}
236+
193237
return result;
194238
}
195239
}

0 commit comments

Comments
 (0)