# 4. Modelação da Base de Dados ## 4.1 Diagrama de Entidade-Relacionamento (ERD) ``` ┌──────────────┐ ┌──────────────────┐ ┌──────────────┐ │ User │ │ Animal │ │ Shelter │ ├──────────────┤ ├──────────────────┤ ├──────────────┤ │ id (PK) │ │ id (PK) │ │ id (PK) │ │ email │ │ shelterId (FK) ──┼──────►│ name │ │ password │ │ name │ │ district │ │ name │ │ species │ │ address │ │ birthdate │ │ breed │ │ latitude │ │ district │ │ age │ │ longitude │ │ role │ │ sex │ │ phone │ │ darkMode │ │ sterilized │ │ email │ │ createdAt │ │ status │ │ openHours │ └──────┬───────┘ │ urgent │ │ passwordHash │ │ │ description │ │ createdAt │ │ │ createdAt │ └──────┬───────┘ │ └────────┬─────────┘ │ │ │ │ │ ┌────────▼────────┐ ┌────────▼────────┐ │ │ AnimalPhoto │ │ ShelterNeed │ │ ├─────────────────┤ ├─────────────────┤ │ │ id (PK) │ │ id (PK) │ │ │ animalId (FK) │ │ shelterId (FK) │ │ │ url │ │ type │ │ │ isPrimary │ │ description │ │ └─────────────────┘ │ urgent │ │ │ active │ │ └─────────────────┘ │ ├──────────────────────────────────────────────────────────┐ │ │ ┌──────▼───────────────┐ ┌──────────▼──────┐ │ Reservation │ │ Donation │ ├──────────────────────┤ ├─────────────────┤ │ id (PK) │ │ id (PK) │ │ userId (FK) ─────────┘ │ userId (FK) │ │ animalId (FK) │ │ shelterId (FK) │ │ date │ │ type │ │ status │ │ details (JSON) │ │ emailSent │ │ status │ │ createdAt │ │ createdAt │ └──────────────────────┘ └────────┬────────┘ │ ┌────────▼────────┐ │ Payment │ ├─────────────────┤ │ id (PK) │ │ donationId (FK) │ │ stripeId │ │ amount │ │ currency │ │ status │ │ createdAt │ └─────────────────┘ ``` --- ## 4.2 Descrição das Entidades | Entidade | Descrição | Relações | |---|---|---| | `User` | Utilizador registado na plataforma | Tem muitas `Reservation` e `Donation` | | `Shelter` | Canil ou associação de protecção animal | Tem muitos `Animal`, recebe `Donation`, tem `ShelterNeed` | | `Animal` | Animal disponível para adopção | Pertence a um `Shelter`, tem `AnimalPhoto` e `Reservation` | | `Reservation` | Reserva de adopção de um animal | Pertence a `User` e `Animal` | | `Donation` | Doação (monetária, ração ou brinquedos) | Pertence a `User` e `Shelter`, tem `Payment` opcional | | `Payment` | Registo do pagamento processado pelo Stripe | Associado a `Donation` do tipo monetário | | `AnimalPhoto` | Fotografia de um animal | Pertence a `Animal` | | `ShelterNeed` | Necessidade actual do canil | Pertence a `Shelter` | --- ## 4.3 Esquema Prisma Completo ```prisma // prisma/schema.prisma generator client { provider = "prisma-client-js" } datasource db { provider = "postgresql" url = env("DATABASE_URL") } // ─── Enums ────────────────────────────────────────────────────── enum UserRole { USER SHELTER_ADMIN ADMIN } enum Species { DOG CAT OTHER } enum Sex { MALE FEMALE } enum AnimalStatus { AVAILABLE RESERVED ADOPTED } enum DonationType { MONETARY FOOD TOYS } enum ReservationStatus { PENDING CONFIRMED CANCELLED COMPLETED } enum DonationStatus { PENDING CONFIRMED DELIVERED CANCELLED } // ─── Models ───────────────────────────────────────────────────── model User { id String @id @default(cuid()) email String @unique password String // bcrypt hash — nunca em texto simples name String birthdate DateTime district String role UserRole @default(USER) darkMode Boolean @default(false) emailVerified Boolean @default(false) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt reservations Reservation[] donations Donation[] @@index([email]) @@index([district]) } model Shelter { id String @id @default(cuid()) name String district String address String latitude Float longitude Float phone String email String @unique passwordHash String description String? website String? // JSON: { mon: "09:00-18:00", tue: "09:00-18:00", ... , sun: null } openHours Json verified Boolean @default(false) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt animals Animal[] donations Donation[] needs ShelterNeed[] @@index([district]) } model Animal { id String @id @default(cuid()) shelterId String shelter Shelter @relation(fields: [shelterId], references: [id]) name String species Species breed String? // relevante para DOG e CAT ageMonths Int // idade em meses para precisão sex Sex sterilized Boolean status AnimalStatus @default(AVAILABLE) urgent Boolean @default(false) description String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt photos AnimalPhoto[] reservations Reservation[] @@index([shelterId]) @@index([species]) @@index([status]) @@index([district: false]) // via shelter — para queries por localidade } model AnimalPhoto { id String @id @default(cuid()) animalId String animal Animal @relation(fields: [animalId], references: [id], onDelete: Cascade) url String isPrimary Boolean @default(false) createdAt DateTime @default(now()) @@index([animalId]) } model Reservation { id String @id @default(cuid()) userId String user User @relation(fields: [userId], references: [id]) animalId String animal Animal @relation(fields: [animalId], references: [id]) date DateTime status ReservationStatus @default(PENDING) emailSent Boolean @default(false) notes String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@index([userId]) @@index([animalId]) @@index([date]) } model Donation { id String @id @default(cuid()) userId String user User @relation(fields: [userId], references: [id]) shelterId String shelter Shelter @relation(fields: [shelterId], references: [id]) type DonationType // Para FOOD: { foodType: "dry|wet", animalType: "dog|cat", ageGroup: "adult|puppy" } // Para TOYS: { category: "chew|plush|interactive" } // Para MONETARY: { amount: 25.00, currency: "EUR" } // Partilhado: { deliveryMethod: "pickup|home_delivery", address?: "..." } details Json status DonationStatus @default(PENDING) emailSent Boolean @default(false) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt payment Payment? @@index([userId]) @@index([shelterId]) @@index([type]) } model Payment { id String @id @default(cuid()) donationId String @unique donation Donation @relation(fields: [donationId], references: [id]) stripePaymentId String @unique amount Int // em cêntimos (ex: 2500 = 25,00€) currency String @default("eur") status String // stripe payment status receiptUrl String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } model ShelterNeed { id String @id @default(cuid()) shelterId String shelter Shelter @relation(fields: [shelterId], references: [id]) type String // "food_dry_dog", "toys", "blankets", etc. description String? urgent Boolean @default(false) active Boolean @default(true) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@index([shelterId]) @@index([active]) } ``` --- ## 4.4 Estratégia de Indexação Os índices foram definidos com base nos filtros mais comuns da aplicação: | Índice | Tabela | Motivo | |---|---|---| | `email` | User | Login — lookup por email frequente | | `district` | User, Shelter | Filtros por localidade | | `shelterId` | Animal | Listar animais de um canil | | `species`, `status` | Animal | Filtros principais da listagem | | `userId` | Reservation, Donation | Histórico do utilizador | | `animalId` | Reservation | Verificar disponibilidade do animal | | `type`, `active` | ShelterNeed | Listar necessidades activas por tipo | --- ## 4.5 Migrações As migrações são geridas automaticamente pelo Prisma: ```bash # Criar nova migração após alterar schema.prisma npx prisma migrate dev --name descricao_da_alteracao # Aplicar migrações em produção npx prisma migrate deploy # Visualizar base de dados no browser (Prisma Studio) npx prisma studio ```