This document provides a comprehensive overview of the StellarAid database schema design, including all models, relationships, constraints, and design decisions.
Status: ✅ Schema validated and ready for first migration
Defines user types in the system:
- DONOR: Users who contribute funds to campaigns
- CREATOR: Users who create fundraising campaigns
- ADMIN: Administrative users with elevated privileges
Tracks the lifecycle of campaigns:
- DRAFT: Campaign not yet submitted for approval
- PENDING_APPROVAL: Awaiting admin review
- ACTIVE: Approved and actively fundraising
- COMPLETED: Campaign goal reached or deadline passed
- CANCELLED: Creator cancelled the campaign
- REJECTED: Admin rejected the campaign
Tracks donation transaction states:
- PENDING: Donation initiated, awaiting blockchain confirmation
- CONFIRMED: Donation successfully confirmed on blockchain
- REFUNDED: Donor initiated a refund
- FAILED: Donation transaction failed
Tracks milestone progress:
- PENDING: Milestone not yet active
- ACTIVE: Milestone currently in progress
- COMPLETED: Milestone targets achieved
- FAILED: Milestone target not reached by deadline
Types of notifications sent to users:
- CAMPAIGN_CREATED: New campaign notification
- CAMPAIGN_UPDATED: Campaign update notification
- DONATION_RECEIVED: Campaign creator notified of donation
- MILESTONE_REACHED: Notification when milestone is achieved
- CAMPAIGN_COMPLETED: Campaign completion notification
- DISPUTE_FILED: Dispute notification
- DISPUTE_RESOLVED: Dispute resolution notification
Tracks dispute lifecycle:
- OPENED: Dispute freshly filed
- UNDER_REVIEW: Admin reviewing the dispute
- RESOLVED: Dispute has been resolved
- REJECTED: Dispute was invalid or rejected
Types of auditable actions:
- USER_CREATED: New user registration
- CAMPAIGN_CREATED: New campaign created
- CAMPAIGN_UPDATED: Campaign details modified
- DONATION_MADE: Donation transaction recorded
- MILESTONE_COMPLETED: Milestone marked complete
- DISPUTE_FILED: New dispute opened
- ADMIN_ACTION: Generic admin action
Core user entity representing donors, creators, and administrators.
Fields:
id(UUID, PK): Unique identifieremail(String, UNIQUE): User email addressname(String, NULLABLE): User display namerole(UserRole, DEFAULT: DONOR): User typewalletAddress(String, UNIQUE, NULLABLE): Stellar blockchain walletbio(String, NULLABLE): User biographyisActive(Boolean, DEFAULT: true): Account activation statuscreatedAt(DateTime, DEFAULT: now()): Account creation timestampupdatedAt(DateTime, AUTO): Last update timestamp
Relationships:
campaigns← Campaign (one-to-many): Campaigns created by userdonations← Donation (one-to-many): Donations made by userupdates← Update (one-to-many): Campaign updates posted by usernotifications← Notification (one-to-many): Notifications for userdisputes← Dispute (one-to-many): Disputes filed by usernewsLetterSub← Newsletter (one-to-one, NULLABLE): Newsletter subscriptionauditLogs← AuditLog (one-to-many): Audit logs generated by user
Indices:
email: Quick user lookupsrole: Filter users by roleisActive: Filter active/inactive users
Constraints:
- Email must be unique (prevents duplicate accounts)
- Wallet address must be unique (one wallet per user)
Represents fundraising campaigns on the platform.
Fields:
id(UUID, PK): Unique identifiertitle(String): Campaign titledescription(String): Campaign descriptiongoalAmount(Decimal): Target fundraising amountraisedAmount(Decimal, DEFAULT: 0): Current amount raisedstatus(CampaignStatus, DEFAULT: DRAFT): Campaign statecreatorId(String, FK): Reference to campaign creatorstartDate(DateTime, NULLABLE): Campaign start dateendDate(DateTime, NULLABLE): Campaign deadlineimageUrl(String, NULLABLE): Campaign cover image URLcategory(String, NULLABLE): Campaign category/tagcreatedAt(DateTime, DEFAULT: now()): Creation timestampupdatedAt(DateTime, AUTO): Last update timestamp
Relationships:
creator→ User: Creator of campaign (REQUIRED, CASCADE delete)donations← Donation (one-to-many): Donations receivedmilestones← Milestone (one-to-many): Campaign milestonesupdates← Update (one-to-many): Campaign progress updatesdisputes← Dispute (one-to-many): Related disputes
Indices:
creatorId: Find campaigns by creatorstatus: Filter by campaign statuscreatedAt: Sort campaigns chronologicallycategory: Filter by category
Constraints:
- Creator reference is required and cascades on delete
- raisedAmount tracks total donations (updated programmatically)
Individual donations from donors to campaigns.
Fields:
id(UUID, PK): Unique identifieramount(Decimal): Donation amountassetCode(String, DEFAULT: "XLM"): Blockchain asset codetxHash(String, UNIQUE, NULLABLE): Blockchain transaction hashstatus(DonationStatus, DEFAULT: PENDING): Donation statedonorId(String, FK): Reference to donorcampaignId(String, FK): Reference to target campaigndonatedAt(DateTime, DEFAULT: now()): Donation timestampconfirmedAt(DateTime, NULLABLE): Confirmation timestampcreatedAt(DateTime, DEFAULT: now()): Record creation timeupdatedAt(DateTime, AUTO): Last update timestamp
Relationships:
donor→ User: Donation maker (REQUIRED, CASCADE delete)campaign→ Campaign: Target campaign (REQUIRED, CASCADE delete)disputes← Dispute (one-to-many, NULLABLE): Related disputes
Indices:
donorId: Find donations by donorcampaignId: Find donations to campaignstatus: Filter by statuscreatedAt: Sort by date
Constraints:
- Composite Unique:
(donorId, campaignId, txHash)- prevents duplicate donations - Singular Unique:
txHash- blockchain transaction hash is globally unique - Both foreign keys cascade on delete
Campaign milestone tracking for goal progression.
Fields:
id(UUID, PK): Unique identifiercampaignId(String, FK): Reference to parent campaigntitle(String): Milestone titledescription(String, NULLABLE): Milestone descriptiontargetAmount(Decimal): Amount to raise for this milestonestatus(MilestoneStatus, DEFAULT: PENDING): Milestone statedueDate(DateTime, NULLABLE): Target completion datecompletedAt(DateTime, NULLABLE): Actual completion timestampcreatedAt(DateTime, DEFAULT: now()): Creation timestampupdatedAt(DateTime, AUTO): Last update timestamp
Relationships:
campaign→ Campaign: Parent campaign (REQUIRED, CASCADE delete)
Indices:
campaignId: Find milestones for campaignstatus: Filter by milestone status
Constraints:
- Campaign reference required and cascades on delete
Campaign progress updates posted by creators.
Fields:
id(UUID, PK): Unique identifiercampaignId(String, FK): Reference to campaigncreatorId(String, FK): Reference to update authortitle(String): Update titlecontent(String): Update content/bodyimageUrl(String, NULLABLE): Update image URLcreatedAt(DateTime, DEFAULT: now()): Creation timestampupdatedAt(DateTime, AUTO): Last update timestamp
Relationships:
campaign→ Campaign: Parent campaign (REQUIRED, CASCADE delete)creator→ User: Update author (REQUIRED, CASCADE delete)
Indices:
campaignId: Find updates for campaigncreatorId: Find updates by creatorcreatedAt: Sort chronologically
Constraints:
- Both foreign keys cascade on delete
User notifications system.
Fields:
id(UUID, PK): Unique identifieruserId(String, FK): Reference to recipienttype(NotificationType): Notification categorytitle(String): Notification titlemessage(String): Notification messagerelatedId(String, NULLABLE): ID of related resource (campaign, donation, etc.)isRead(Boolean, DEFAULT: false): Read statuscreatedAt(DateTime, DEFAULT: now()): Creation timestampupdatedAt(DateTime, AUTO): Last update timestamp
Relationships:
user→ User: Notification recipient (REQUIRED, CASCADE delete)
Indices:
userId: Find notifications for userisRead: Filter read/unread notificationscreatedAt: Sort by date
Constraints:
- User reference required and cascades on delete
Donation dispute tracking and resolution.
Fields:
id(UUID, PK): Unique identifierdonationId(String, FK, UNIQUE): Reference to disputed donationfilerId(String, FK): Reference to dispute filercampaignId(String, FK): Reference to campaignreason(String): Reason for disputedescription(String): Detailed descriptionstatus(DisputeStatus, DEFAULT: OPENED): Dispute stateresolution(String, NULLABLE): Resolution detailsresolvedAt(DateTime, NULLABLE): Resolution timestampcreatedAt(DateTime, DEFAULT: now()): Creation timestampupdatedAt(DateTime, AUTO): Last update timestamp
Relationships:
donation→ Donation: Disputed donation (REQUIRED, CASCADE delete)filer→ User: Dispute filer (REQUIRED, CASCADE delete)campaign→ Campaign: Related campaign (REQUIRED, CASCADE delete)
Indices:
filerId: Find disputes by filercampaignId: Find disputes for campaignstatus: Filter by dispute statuscreatedAt: Sort by date
Constraints:
- Unique:
donationId- only one active dispute per donation - All foreign keys cascade on delete
Newsletter subscription management.
Fields:
id(UUID, PK): Unique identifieruserId(String, FK, UNIQUE): Reference to subscriberemail(String, UNIQUE): Subscriber email addressisSubscribed(Boolean, DEFAULT: true): Subscription statussubscribedAt(DateTime, DEFAULT: now()): Subscription timestampunsubscribedAt(DateTime, NULLABLE): Unsubscription timestampcreatedAt(DateTime, DEFAULT: now()): Creation timestampupdatedAt(DateTime, AUTO): Last update timestamp
Relationships:
user→ User: Subscriber (REQUIRED, CASCADE delete)
Indices:
email: Quick email lookupsisSubscribed: Find active subscribers
Constraints:
- Both
userIdandemailare unique (one subscription per user/email) - User reference cascades on delete
System audit trail for compliance and debugging.
Fields:
id(UUID, PK): Unique identifieruserId(String, FK, NULLABLE): Reference to user performing actionaction(AuditActionType): Type of actionresourceType(String): Type of resource affectedresourceId(String): ID of affected resourcedetails(String, NULLABLE): JSON stringified additional detailsipAddress(String, NULLABLE): IP address of request originuserAgent(String, NULLABLE): Browser/client user agentcreatedAt(DateTime, DEFAULT: now()): Action timestamp
Relationships:
user→ User: User performing action (NULLABLE, SET NULL on delete)
Indices:
userId: Find actions by useraction: Filter by action typeresourceType: Filter by resource typecreatedAt: Sort chronologically
Constraints:
- User reference is optional (allows recording actions even after user deletion)
- Uses SET NULL on delete instead of CASCADE (preserves audit history)
The following models cascade delete when parent is deleted:
- Campaign → Donations, Milestones, Updates, Disputes
- Donation → Disputes
- User → Campaigns, Donations, Updates, Notifications, Newsletter
This ensures:
- ✅ Data consistency (no orphaned records)
- ✅ Clean removal of resources and all dependencies
- ✅ Automatic cleanup of related data
- NOT NULL constraints on all foreign key fields (except AuditLog.userId)
- CASCADE DELETE enforces relationship integrity
- UNIQUE constraints prevent duplicate entries
- All models include
createdAtandupdatedAttimestamps - AuditLog tracks all significant actions
- IP addresses and user agents captured for security
- Composite unique constraints (e.g., Donation) prevent duplicates
- Status fields maintain valid state transitions
- raisedAmount in Campaign updated programmatically
All foreign keys indexed for:
- Fast relationship lookups (JOIN performance)
- Quick filtering by creator/donor
- Efficient sorting and pagination
Category indices for:
- Campaign filtering and discovery
- User role-based queries
Timestamp indices for:
- Chronological sorting
- Date-range queries
- Audit trail navigation
- Campaign Discovery: Use compound indices (status, createdAt)
- User Donations: Filter by (donorId, status, createdAt)
- Campaign Milestones: Query by campaignId first, then status
- Notifications: Always filter by userId first
- Comments: Add Comment model for community engagement
- Reviews/Ratings: Add Review model for campaign ratings
- Tags: Expand category field or add Tags model
- Social Links: Extend User model with social profiles
- Media Gallery: Extend Update with multiple media support
- Payment Methods: Add PaymentMethod model for wallet diversity
This schema is designed to support incremental feature additions without major restructuring.
Before running the first migration:
- All 9 models defined: User, Campaign, Donation, Milestone, Update, Notification, Dispute, Newsletter, AuditLog
- All required enums: UserRole, CampaignStatus, DonationStatus, MilestoneStatus, NotificationType, DisputeStatus, AuditActionType
- Foreign keys and relationships defined
- Cascade delete strategies implemented
- Composite and unique constraints in place
- Strategic indices on all foreign keys and frequently-queried fields
- Timestamp tracking on all models
- Schema validation passes: ✅
- Schema reviewed and approved by team
- First migration created:
npx prisma migrate dev --name init - Database seeded with test data (optional)
- TypeScript types generated:
npx prisma generate
Total Models: 9 Total Enums: 7 Total Relationships: 22 Indices: 30+ Constraints: 12+
Design Principles Applied:
- ✅ Single Responsibility: Each model has a clear, focused purpose
- ✅ Domain Consistency: Naming and structure reflect StellarAid domain
- ✅ Referential Integrity: Strong relationships with cascading deletes
- ✅ Audit Trail: AuditLog captures all significant actions
- ✅ Performance: Strategic indices on all foreign keys and search fields
- ✅ Extensibility: Flexible design for future features
- ✅ Data Consistency: Unique constraints and status enums ensure valid states