371 lines
6.8 KiB
Markdown
371 lines
6.8 KiB
Markdown
# Database Models
|
|
|
|
Changemaker Lite V2 uses a comprehensive PostgreSQL database schema with 30+ models across authentication, campaigns, locations, media, and content management. The schema is managed via Prisma ORM (main API) and Drizzle ORM (media API).
|
|
|
|
## Model Organization
|
|
|
|
Models are organized by feature area:
|
|
|
|
### [Authentication & Users](auth.md)
|
|
|
|
Core authentication and user management:
|
|
|
|
- **User** - User accounts with roles and authentication
|
|
- **RefreshToken** - JWT refresh token tracking
|
|
- **Session** - User session management (future)
|
|
|
|
### [Influence Module](influence.md)
|
|
|
|
Advocacy campaign models:
|
|
|
|
- **Campaign** - Campaign definitions and settings
|
|
- **CampaignEmail** - Sent email tracking
|
|
- **Response** - Public response wall submissions
|
|
- **PostalCodeCache** - Representative lookup cache
|
|
|
|
### [Map Module](map.md)
|
|
|
|
Location and geographic models:
|
|
|
|
- **Location** - Address database with geocoding
|
|
- **Cut** - Geographic polygon organization
|
|
- **Shift** - Volunteer shift scheduling
|
|
- **MapSettings** - Map configuration singleton
|
|
|
|
### [Canvassing](canvass.md)
|
|
|
|
Door-to-door canvassing models:
|
|
|
|
- **CanvassSession** - Canvassing session tracking
|
|
- **CanvassVisit** - Visit outcome recording
|
|
- **TrackingSession** - GPS tracking (future)
|
|
|
|
### [Content Management](pages.md)
|
|
|
|
Landing pages and content:
|
|
|
|
- **Page** - Landing page definitions
|
|
- **PageBlock** - Reusable content blocks
|
|
|
|
### [Email Templates](email-templates.md)
|
|
|
|
Email template system:
|
|
|
|
- **EmailTemplate** - Template definitions
|
|
- **EmailTemplateVersion** - Version history (future)
|
|
|
|
### [Media](media.md)
|
|
|
|
Video library (Drizzle ORM):
|
|
|
|
- **videos** - Video metadata and files
|
|
- **shared_media** - Public gallery assignments
|
|
- **media_reactions** - Emoji reactions
|
|
- **media_jobs** - Background job queue
|
|
|
|
### [Settings](settings.md)
|
|
|
|
Global configuration:
|
|
|
|
- **Settings** - Site-wide settings singleton
|
|
|
|
## ORM Architecture
|
|
|
|
### Prisma (Main API)
|
|
|
|
Used for 95% of models:
|
|
|
|
- **Schema:** `api/prisma/schema.prisma`
|
|
- **Migrations:** `api/prisma/migrations/`
|
|
- **Client:** Auto-generated TypeScript types
|
|
- **Database:** PostgreSQL 16
|
|
|
|
### Drizzle (Media API)
|
|
|
|
Used for media models only:
|
|
|
|
- **Schema:** `api/src/modules/media/db/schema.ts`
|
|
- **Migrations:** None (push-based)
|
|
- **Client:** Manual schema definition
|
|
- **Database:** Same PostgreSQL 16
|
|
|
|
## Common Patterns
|
|
|
|
### Timestamps
|
|
|
|
Most models include:
|
|
|
|
```prisma
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
```
|
|
|
|
### Foreign Keys
|
|
|
|
Relations use explicit foreign key fields:
|
|
|
|
```prisma
|
|
model Campaign {
|
|
id Int @id @default(autoincrement())
|
|
createdByUserId Int
|
|
createdBy User @relation(fields: [createdByUserId], references: [id])
|
|
}
|
|
```
|
|
|
|
### JSON Fields
|
|
|
|
Flexible data stored as JSON:
|
|
|
|
```prisma
|
|
model Campaign {
|
|
emailTemplate Json?
|
|
settings Json?
|
|
}
|
|
```
|
|
|
|
TypeScript types:
|
|
|
|
```typescript
|
|
import { Prisma } from '@prisma/client';
|
|
|
|
const template: Prisma.InputJsonValue = {
|
|
subject: 'Email subject',
|
|
body: 'Email body',
|
|
};
|
|
```
|
|
|
|
### Enums
|
|
|
|
Type-safe enumerations:
|
|
|
|
```prisma
|
|
enum Role {
|
|
SUPER_ADMIN
|
|
INFLUENCE_ADMIN
|
|
MAP_ADMIN
|
|
USER
|
|
TEMP
|
|
}
|
|
|
|
enum VisitOutcome {
|
|
SUCCESS
|
|
NOT_HOME
|
|
MOVED
|
|
REFUSED
|
|
WRONG_ADDRESS
|
|
INACCESSIBLE
|
|
OTHER
|
|
}
|
|
```
|
|
|
|
## Model Count by Category
|
|
|
|
| Category | Models | ORM |
|
|
|----------|--------|-----|
|
|
| Authentication | 3 | Prisma |
|
|
| Influence | 4 | Prisma |
|
|
| Map | 4 | Prisma |
|
|
| Canvassing | 3 | Prisma |
|
|
| Content | 2 | Prisma |
|
|
| Email Templates | 2 | Prisma |
|
|
| Media | 4 | Drizzle |
|
|
| Settings | 2 | Prisma |
|
|
| **Total** | **24** | Mixed |
|
|
|
|
## Database Operations
|
|
|
|
### Migrations (Prisma)
|
|
|
|
```bash
|
|
# Create migration
|
|
cd api && npx prisma migrate dev --name add_field
|
|
|
|
# Deploy migrations
|
|
cd api && npx prisma migrate deploy
|
|
|
|
# Reset database (dev only)
|
|
cd api && npx prisma migrate reset
|
|
```
|
|
|
|
### Schema Push (Drizzle)
|
|
|
|
```bash
|
|
# Push schema changes (media API)
|
|
cd api && npx drizzle-kit push
|
|
```
|
|
|
|
### Database Browser
|
|
|
|
View data via:
|
|
|
|
- **Prisma Studio:** `npx prisma studio`
|
|
- **NocoDB:** http://localhost:8091 (read-only)
|
|
|
|
## Indexes
|
|
|
|
Key indexes for performance:
|
|
|
|
```prisma
|
|
model Location {
|
|
@@index([cutId])
|
|
@@index([lastVisitedAt])
|
|
}
|
|
|
|
model Campaign {
|
|
@@index([published])
|
|
@@index([createdByUserId])
|
|
}
|
|
|
|
model CanvassSession {
|
|
@@index([userId])
|
|
@@index([status])
|
|
}
|
|
```
|
|
|
|
## Constraints
|
|
|
|
### Unique Constraints
|
|
|
|
```prisma
|
|
model User {
|
|
email String @unique
|
|
}
|
|
|
|
model Page {
|
|
slug String @unique
|
|
}
|
|
|
|
model Cut {
|
|
name String @unique
|
|
}
|
|
```
|
|
|
|
### Check Constraints
|
|
|
|
Enforced at application level:
|
|
|
|
- Email format validation
|
|
- Password complexity (12+ chars)
|
|
- Coordinate bounds (-90 to 90 lat, -180 to 180 lng)
|
|
|
|
## Relations
|
|
|
|
### One-to-Many
|
|
|
|
```prisma
|
|
model User {
|
|
id Int @id @default(autoincrement())
|
|
campaigns Campaign[]
|
|
}
|
|
|
|
model Campaign {
|
|
id Int @id @default(autoincrement())
|
|
createdByUserId Int
|
|
createdBy User @relation(fields: [createdByUserId], references: [id])
|
|
}
|
|
```
|
|
|
|
### Many-to-Many
|
|
|
|
Via junction tables:
|
|
|
|
```prisma
|
|
model Shift {
|
|
id Int @id @default(autoincrement())
|
|
signups ShiftSignup[]
|
|
}
|
|
|
|
model User {
|
|
id Int @id @default(autoincrement())
|
|
signups ShiftSignup[]
|
|
}
|
|
|
|
model ShiftSignup {
|
|
id Int @id @default(autoincrement())
|
|
shiftId Int
|
|
userId Int
|
|
shift Shift @relation(fields: [shiftId], references: [id])
|
|
user User @relation(fields: [userId], references: [id])
|
|
|
|
@@unique([shiftId, userId])
|
|
}
|
|
```
|
|
|
|
## Seeding
|
|
|
|
Initial data in `api/prisma/seed.ts`:
|
|
|
|
- Admin user (admin@example.com)
|
|
- Default settings
|
|
- Sample page blocks
|
|
- System email templates
|
|
|
|
```bash
|
|
# Run seed
|
|
cd api && npx prisma db seed
|
|
```
|
|
|
|
## Data Types
|
|
|
|
### Common Types
|
|
|
|
- **ID:** `Int @id @default(autoincrement())`
|
|
- **String:** `String` or `String @db.Text` (long text)
|
|
- **Number:** `Int` or `Float`
|
|
- **Boolean:** `Boolean @default(false)`
|
|
- **Date:** `DateTime @default(now())`
|
|
- **JSON:** `Json` or `Json?`
|
|
- **Enum:** `Role`, `VisitOutcome`, etc.
|
|
|
|
### Spatial Data
|
|
|
|
GeoJSON stored as JSON:
|
|
|
|
```prisma
|
|
model Cut {
|
|
geometry Json // GeoJSON Polygon
|
|
}
|
|
```
|
|
|
|
Coordinates as separate fields:
|
|
|
|
```prisma
|
|
model Location {
|
|
latitude Float
|
|
longitude Float
|
|
}
|
|
```
|
|
|
|
## Database Configuration
|
|
|
|
### Connection String
|
|
|
|
```bash
|
|
DATABASE_URL="postgresql://user:password@localhost:5432/changemaker_v2?schema=public"
|
|
```
|
|
|
|
### Connection Pool
|
|
|
|
Prisma connection pool:
|
|
|
|
```typescript
|
|
// api/src/server.ts
|
|
const prisma = new PrismaClient({
|
|
log: ['error', 'warn'],
|
|
});
|
|
```
|
|
|
|
## Related Documentation
|
|
|
|
- [Authentication Models](auth.md)
|
|
- [Influence Models](influence.md)
|
|
- [Map Models](map.md)
|
|
- [Canvassing Models](canvass.md)
|
|
- [Content Models](pages.md)
|
|
- [Email Template Models](email-templates.md)
|
|
- [Media Models](media.md)
|
|
- [Settings Models](settings.md)
|
|
- [Database Overview](../index.md)
|
|
- [Migrations Guide](../../development/migrations.md)
|
|
- [Backend Modules](../../backend/modules/index.md)
|