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>
33 lines
1.2 KiB
TypeScript
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] }),
|
|
}));
|