Files
website/src/lib/server/db/schema/animaldex.ts
Stan Runge 64fb8df262 db: Drizzle schemas on domain Postgres schemas, baseline migration, reorg SQL
- pgSchema tables for auth, money, house, ref, games, cs, animaldex, site, meta
  (lists excluded via schemaFilter; runtime list tables are managed by meta.*)
- drizzle/ baseline matches the reorged DB exactly (catalog diff = 0 on rehearsal)
- db/reorg: one-time SQL that moves the flat public schema (rehearsed on a prod copy)
- scripts/db-migrate.ts: migrator with --init to record the baseline and a guard
  against running the baseline on a non-empty, unrecorded database
2026-10-23 22:03:42 +00:00

71 lines
2.3 KiB
TypeScript

import { doublePrecision, integer, text } from 'drizzle-orm/pg-core';
import { animaldexSchema } from './schemas';
import { id, timestamps, timestamptz } from './helpers';
export const animals = animaldexSchema.table('animals', {
id: id(),
species: text('species').notNull(),
breed: text('breed'),
animalName: text('animal_name'),
description: text('description'),
// AI detection stored on the animal (from the first/primary photo)
aiBreedSuggestion: text('ai_breed_suggestion'),
aiBreedConfidence: doublePrecision('ai_breed_confidence'),
// Moderation — the animal entity is moderated once.
// Accepted = visible on map (as long as it has accepted sightings too).
// Denied animals are hidden but not deleted.
submittedAt: timestamptz('submitted_at').notNull().defaultNow(),
acceptedAt: timestamptz('accepted_at'),
deniedAt: timestamptz('denied_at'),
...timestamps
});
export const sightings = animaldexSchema.table('sightings', {
id: id(),
animalId: integer('animal_id')
.notNull()
.references(() => animals.id, { onDelete: 'cascade' }),
reporterName: text('reporter_name'),
seenAt: timestamptz('seen_at').notNull(),
lat: doublePrecision('lat').notNull(),
lng: doublePrecision('lng').notNull(),
submittedAt: timestamptz('submitted_at').notNull().defaultNow(),
acceptedAt: timestamptz('accepted_at'),
deniedAt: timestamptz('denied_at'),
...timestamps
});
export const photos = animaldexSchema.table('sighting_photos', {
id: id(),
sightingId: integer('sighting_id')
.notNull()
.references(() => sightings.id, { onDelete: 'cascade' }),
r2Key: text('r2_key').notNull(),
url: text('url').notNull(),
sortOrder: integer('sort_order').notNull().default(0),
uploadedAt: timestamptz('uploaded_at').notNull().defaultNow(),
...timestamps
});
export type Animal = typeof animals.$inferSelect;
export type NewAnimal = typeof animals.$inferInsert;
export type Sighting = typeof sightings.$inferSelect;
export type NewSighting = typeof sightings.$inferInsert;
export type Photo = typeof photos.$inferSelect;
export type ModerationStatus = 'pending' | 'accepted' | 'denied';
export function getModerationStatus(
row: Pick<Animal | Sighting, 'acceptedAt' | 'deniedAt'>
): ModerationStatus {
if (row.deniedAt) return 'denied';
if (row.acceptedAt) return 'accepted';
return 'pending';
}