Files
linkder/packages/db/src/schema/messaging.ts
T
serfowiandClaude Opus 5 19623bcccb M0: foundation — monorepo, PostGIS schema, deck query, app shell
Greenfield scaffold for Linkder, a swipe-to-hire marketplace for local
professional services.

- pnpm/turbo monorepo: apps/web, packages/{shared,db}
- Postgres 16 + PostGIS via docker compose (ports 5442/6389 to avoid
  clashing with other local stacks)
- Drizzle schema, 23 tables, geography(Point,4326) with GiST indexes
- Domain core in packages/shared: integer-cent money, status transition
  graphs, deck ranking weights, cancellation policy — 46 unit tests
- Deck query: filtering in Postgres on the GiST index, ranking in JS so
  the weights stay tunable — 18 integration tests against a seeded DB
- Deterministic seed placing pros at known distances, including three
  that must NOT appear on a deck (out of radius, unverified, away)
- Next.js 15 app shell with a working swipe deck
- CI: typecheck, lint, test, build against live postgres+redis

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-20 13:32:35 -04:00

33 lines
1.2 KiB
TypeScript

import { relations, sql } from 'drizzle-orm';
import { index, pgTable, text, timestamp, uuid } from 'drizzle-orm/pg-core';
import { users } from './auth';
import { matches } from './matching';
export const messages = pgTable(
'messages',
{
id: uuid('id').primaryKey().defaultRandom(),
matchId: uuid('match_id')
.notNull()
.references(() => matches.id, { onDelete: 'cascade' }),
senderId: uuid('sender_id')
.notNull()
.references(() => users.id, { onDelete: 'cascade' }),
body: text('body').notNull(),
attachments: text('attachments').array().notNull().default(sql`'{}'::text[]`),
readAt: timestamp('read_at', { withTimezone: true }),
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
},
(t) => [
// Chat history is always "this match, newest last".
index('messages_match_idx').on(t.matchId, t.createdAt),
// Unread badge count.
index('messages_unread_idx').on(t.matchId, t.senderId).where(sql`${t.readAt} IS NULL`),
],
);
export const messagesRelations = relations(messages, ({ one }) => ({
match: one(matches, { fields: [messages.matchId], references: [matches.id] }),
sender: one(users, { fields: [messages.senderId], references: [users.id] }),
}));