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>
This commit is contained in:
@@ -0,0 +1,32 @@
|
||||
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] }),
|
||||
}));
|
||||
Reference in New Issue
Block a user