Worldview is a quadratic voting platform based on World mini-apps that allows users to create, manage, and participate in polls. The platform enables community-driven decision making through a weighted voting system, giving users the ability to express preference strength across multiple options.
- Poll creation with draft capability
- Quadratic voting system for more representative outcomes
- User authentication using World ID
- Poll searching, filtering, and sorting
- Vote management for users
- Anonymous voting option
- Production: https://backend.worldview.fyi
- Staging: https://backend.staging.worldview.fyi
- Framework: NestJS (Node.js)
- Database: PostgreSQL with Prisma ORM
- Authentication: JWT, World ID integration
- Voting System: Quadratic voting with normalization options
- Docker: Container-based deployment
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ │ │ │ │ │
│ Frontend App │<────>│ NestJS API │<────>│ PostgreSQL DB │
│ │ │ │ │ │
└─────────────────┘ └─────────────────┘ └─────────────────┘
│
│
┌───────▼───────┐
│ │
│ World ID │
│ Integration │
│ │
└───────────────┘
- Users authenticate via World ID
- Authenticated users can create polls (draft or published)
- Users can vote on active polls using quadratic voting
- The system normalizes and calculates voting weights (based on feature flag)
- Poll results are available for viewing based on permissions
- Node.js (v18+)
- PostgreSQL (v14+)
- Yarn package manager
# Install dependencies
$ yarn install
# Generate Prisma client
$ npx prisma generate
# Run database migrations
$ npx prisma migrate dev
Create a .env
file in the root directory with the following variables:
DATABASE_URL="postgresql://user:password@localhost:5432/worldview"
JWT_SECRET="your-secret-key"
WHITELISTED_ORIGINS="localhost:3000,yourdomain.com"
ENABLE_VOTE_NORMALIZATION=true
TUNNEL_DOMAINS="localtunnel.me,ngrok.io"
# Development mode
$ yarn run start:dev
# Production mode
$ yarn run build
$ yarn run start:prod
# Generate a new migration
$ yarn migration:generate
# Apply migrations (production)
$ yarn migration:prod
GET /auth/nonce
- Get a nonce for World ID authenticationPOST /auth/verifyWorldId
- Verify World ID and authenticate user
POST /poll
- Create a new published pollPATCH /poll/draft
- Create or update a draft pollGET /poll/draft
- Get user's draft pollGET /poll
- Get polls with filtering and pagination- Query params: page, limit, isActive, userVoted, userCreated, search, sortBy, sortOrder
GET /poll/:id
- Get detailed information about a specific pollDELETE /poll/:id
- Delete a poll (if owner)
GET /user/getUserData
- Get user profile dataGET /user/getUserActivities
- Get user activitiesGET /user/getUserVotes
- Get user's voting historyPOST /user/setVote
- Cast a vote on a pollPOST /user/editVote
- Edit an existing votePOST /user/createUser
- Create a new user
model User {
id Int @id @default(autoincrement())
worldID String @unique
name String?
profilePicture String?
pollsCreatedCount Int @default(0)
pollsParticipatedCount Int @default(0)
createdPolls Poll[] @relation("PollAuthor")
actions UserAction[]
votes Vote[]
}
model Poll {
pollId Int @id @default(autoincrement())
authorUserId Int
title String?
description String?
options String[]
creationDate DateTime @default(now())
startDate DateTime?
endDate DateTime?
tags String[]
isAnonymous Boolean @default(false)
participantCount Int @default(0)
voteResults Json
searchVector Unsupported("tsvector")?
status PollStatus @default(PUBLISHED)
author User @relation("PollAuthor", fields: [authorUserId], references: [id])
userAction UserAction[]
votes Vote[]
}
model Vote {
voteID String @id @default(uuid())
userId Int
pollId Int
votingPower Int
weightDistribution Json
proof String
quadraticWeights Json?
normalizedWeightDistribution Json?
normalizedQuadraticWeights Json?
poll Poll @relation(fields: [pollId], references: [pollId], onDelete: Cascade)
user User @relation(fields: [userId], references: [id])
}
model UserAction {
id Int @id @default(autoincrement())
userId Int
pollId Int
type ActionType
createdAt DateTime @default(now())
updatedAt DateTime @default(now()) @updatedAt
poll Poll @relation(fields: [pollId], references: [pollId], onDelete: Cascade)
user User @relation(fields: [userId], references: [id])
}
worldview-be
│
├── prisma/ # Database schema and migrations
│ ├── schema.prisma # Database model definitions
│ └── migrations/ # Database migration files
│
├── src/
│ ├── auth/ # Authentication module
│ │ ├── auth.controller.ts
│ │ ├── auth.service.ts
│ │ ├── jwt.service.ts
│ │ └── jwt-auth.guard.ts
│ │
│ ├── poll/ # Poll management module
│ │ ├── poll.controller.ts
│ │ ├── poll.service.ts
│ │ ├── Poll.dto.ts
│ │ └── poll.module.ts
│ │
│ ├── user/ # User management module
│ │ ├── user.controller.ts
│ │ ├── user.service.ts
│ │ ├── user.dto.ts
│ │ └── user.module.ts
│ │
│ ├── common/ # Shared utilities and exceptions
│ │ ├── exceptions.ts
│ │ ├── http-exception.filter.ts
│ │ └── validators.ts
│ │
│ ├── database/ # Database connection module
│ │ └── database.service.ts
│ │
│ ├── app.module.ts # Main application module
│ ├── app.controller.ts # Main application controller
│ ├── app.service.ts # Main application service
│ └── main.ts # Application entry point
│
├── test/ # Test files
│
├── .env # Environment variables (not in repo)
├── docker-compose.yml # Docker Compose configuration
└── package.json # Project dependencies and scripts
The system allows users to save incomplete polls as drafts before publishing:
- Only one draft poll per user
- Draft polls are not visible to other users
- Fields are optional in draft mode
- Simple conversion from draft to published
Implementation details:
- The Poll entity includes a
status
field with DRAFT/PUBLISHED values patchDraftPoll
endpoint handles creating and updating drafts- Transaction-based approach ensures only one draft exists per user
- Poll queries include status filters to separate draft and published polls
The platform uses quadratic voting for more democratic decision-making:
1. Users distribute voting power across options
2. Final weight = square root of allocated points
3. Optional normalization for fair comparison
Implementation details:
- Vote entity stores both raw weight distribution and quadratic weights
- Optional normalization can be enabled via environment variable
- PostgreSQL functions calculate quadratic weights and normalization
- Results are aggregated with proper weight calculations
- User requests a nonce from the server
- User authenticates with World ID
- Server verifies World ID proof and issues JWT
- JWT token is used for subsequent authenticated requests
The API provides comprehensive filtering options:
- Active/inactive status filter based on poll date
- User participation filters (created/voted)
- Full-text search on title and description using PostgreSQL tsvector
- Custom sorting options (end date, participant count, creation date)
Users can:
- Cast votes with custom weight distribution
- Edit votes during active poll period
- View their voting history
- See vote calculations with quadratic weights
- Controllers: Handle HTTP requests and responses
- Services: Implement business logic and database operations
- DTOs: Define data transfer objects for request/response validation
- Guards: Handle authentication and authorization
- Exceptions: Custom error handling and messages
- Fork the repository
- Create a feature branch
- Submit a pull request with detailed description
- Pass automated tests
- Complete code review
# Build the Docker image
docker-compose build
# Run with Docker Compose
docker-compose up -d
- Development: Uses local database, allows tunnel domains
- Production: Uses secure connections, restricted CORS
- Authentication failures: Check World ID configuration
- Database connection issues: Verify DATABASE_URL
- CORS errors: Update WHITELISTED_ORIGINS
- Vote calculation issues: Check ENABLE_VOTE_NORMALIZATION setting
- Application logs are available in both development and production environments via Grafana (For access please reach out to devOps)