diff --git a/apps/backend/src/db/schema.ts b/apps/backend/src/db/schema.ts index 3305884..59488c4 100644 --- a/apps/backend/src/db/schema.ts +++ b/apps/backend/src/db/schema.ts @@ -110,6 +110,15 @@ export const messageEnvelopes = pgTable( ], ); +export const messageEnvelopes = pgTable('message_envelopes', { + id: uuid('id').primaryKey().defaultRandom(), + messageId: uuid('message_id') + .notNull() + .references(() => messages.id, { onDelete: 'cascade' }), + content: text('content').notNull(), + createdAt: timestamp('created_at').notNull().defaultNow(), +}); + // ─── Devices & prekeys (issues #158, #159, #162) ───────────────────────────── // // Each user may register multiple devices. Each device has an Ed25519 identity @@ -344,6 +353,11 @@ export const devicesRelations = relations(devices, ({ one, many }) => ({ oneTimePreKeys: many(oneTimePreKeys), })); +export const userDevicesRelations = relations(userDevices, ({ one, many }) => ({ + user: one(users, { fields: [userDevices.userId], references: [users.id] }), + messages: many(messages), +})); + export const signedPreKeysRelations = relations(signedPreKeys, ({ one }) => ({ device: one(devices, { fields: [signedPreKeys.deviceId], references: [devices.id] }), })); diff --git a/apps/backend/src/lib/messages.ts b/apps/backend/src/lib/messages.ts index 37601c2..1c27c58 100644 --- a/apps/backend/src/lib/messages.ts +++ b/apps/backend/src/lib/messages.ts @@ -11,7 +11,7 @@ type MessageLike = { [key: string]: any; }; -export function serializeMessage( +export function serializeMessage( message: T, ): Omit & { ciphertext: string | null; diff --git a/apps/backend/src/routes/conversations.ts b/apps/backend/src/routes/conversations.ts index 673385b..19c83f4 100644 --- a/apps/backend/src/routes/conversations.ts +++ b/apps/backend/src/routes/conversations.ts @@ -247,7 +247,9 @@ conversationsRouter.get('/:id/members', async (req: AuthRequest, res) => { with: { user: { columns: { id: true, username: true, avatarUrl: true }, - with: { wallets: { columns: { address: true, isPrimary: true } } }, + with: { + wallets: { columns: { address: true, isPrimary: true } }, + }, }, }, })) as ConversationMemberPayload[]; diff --git a/apps/backend/src/socket/messaging.ts b/apps/backend/src/socket/messaging.ts index 844ce3f..4820b56 100644 --- a/apps/backend/src/socket/messaging.ts +++ b/apps/backend/src/socket/messaging.ts @@ -177,7 +177,11 @@ export function registerMessagingHandlers(io: Server, socket: AuthSocket): void : eq(messages.conversationId, conversationId), orderBy: desc(messages.createdAt), limit: PAGE_SIZE, - with: { sender: { columns: { id: true, username: true, avatarUrl: true } } }, + with: { + envelopes: true, + senderDevice: true, + sender: { columns: { id: true, username: true, avatarUrl: true } }, + }, }); socket.emit('message_history', { @@ -446,6 +450,25 @@ export function registerMessagingHandlers(io: Server, socket: AuthSocket): void ON CONFLICT DO NOTHING `); + // Get or create an assistant device + let assistantDevice = await db.query.userDevices.findFirst({ + where: eq(userDevices.userId, ASSISTANT_USER_ID), + }); + + if (!assistantDevice) { + const [newDevice] = await db + .insert(userDevices) + .values({ + userId: ASSISTANT_USER_ID, + deviceId: 'assistant-device', + deviceName: 'Assistant', + platform: 'web', + identityPublicKey: 'assistant-public-key', + }) + .returning(); + assistantDevice = newDevice; + } + // Post the reply const [replyMessage] = await db .insert(messages)