- 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
71 lines
2.3 KiB
TypeScript
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';
|
|
}
|