ProjectVote is a funding application management system designed to streamline the process of submitting, reviewing, and deciding on project funding requests for associations and organizations.
- Application Submission: User-friendly form for submitting new funding requests.
- Token-Based Voting: Secure, unique voting links for board members to review and cast votes.
- Automated Decision Processing: Automatic status updates and notifications based on board votes.
- Application Archive: A historical record of all funding applications and their outcomes.
Here's a high-level overview of how the application process works in ProjectVote:
graph LR
A[Applicant] -- Submits Application --> B(Application);
B -- Sent to --> C{Board Members};
C -- Cast Votes --> D(Voting Process);
D -- Decision --> E{Automated Decision Processing};
E -- Result --> F(Approved/Rejected);
F -- Sends Email to --> H[Applicant];
F -- Sends Email to --> I[Board Members];
F -- Stored in --> G[Application Archive];
The current user interface (GUI) of ProjectVote is primarily in German. We are keen to make the application accessible to a wider audience by supporting multiple languages.
If you are interested in contributing to the internationalization of ProjectVote, either by providing language packs or helping to implement a robust multilingual system, please refer to our CONTRIBUTING.md for details on how to get involved. Your contributions would be highly valued!
The system automatically determines the application's status as soon as a definitive decision can be reached, even if not all board members have cast their votes. This "finish early" mechanism is based on a dynamic majority threshold that adjusts with the number of abstentions.
The decision logic operates as follows:
- Approval: An application is approved if the number of 'approve' votes meets or exceeds the dynamic majority threshold. Additionally, an application is approved if the current 'approve' votes are greater than the sum of 'reject' votes and all remaining uncast votes, making rejection mathematically impossible.
- Rejection: An application is rejected if the number of 'reject' votes meets or exceeds the dynamic majority threshold. It is also rejected if it's impossible for 'approve' votes to reach the dynamic majority threshold, or if the 'reject' votes are greater than or equal to the sum of 'approve' votes and all remaining uncast votes, making approval mathematically impossible.
- Tie-breaking: In cases where all votes have been cast and there's a tie between 'approve' and 'reject' votes, the application is rejected. If all cast votes are abstentions, the application is also rejected.
Once a final decision (approved or rejected) is reached, email notifications are automatically sent to the applicant and all board members.
The recommended way to run ProjectVote is by using the pre-built Docker images from GitHub Container Registry.
- Docker
- Docker Compose
-
Create a
docker-compose.ymlfile with the following content:services: backend: image: ghcr.io/andreas-vester/projectvote-backend:latest ports: - "8008:8008" volumes: - ./data:/app/data env_file: - .env restart: unless-stopped frontend: image: ghcr.io/andreas-vester/projectvote-frontend:latest ports: - "5173:80" depends_on: - backend restart: unless-stopped
-
Create a
.envfile in the same directory by copying the example below. This file stores sensitive configurations, suchs as board member emails and email server settings.Note: You can also download the example file from the repository.
-
Run the application using Docker Compose:
docker compose up -d
-
Access the application:
- Frontend: http://localhost:5173
- Backend API: http://localhost:8008
The application should now be running. Docker Compose will pull the latest images from the GitHub Container Registry.
We welcome contributions to ProjectVote! If you're interested in fixing bugs, adding new features, or improving the documentation, please see our CONTRIBUTING.md file for details.
To provide a clearer picture of the setup, here are the contents of the main configuration files.
# -----------------------------------------------------------------------------
# Application URLs
# -----------------------------------------------------------------------------
# The public base URL of the frontend application.
# Used for generating links in emails.
FRONTEND_URL=http://your-production-domain.com
# -----------------------------------------------------------------------------
# Board Configuration
# -----------------------------------------------------------------------------
# Comma-separated list of board member email addresses
BOARD_MEMBERS=board.member1@example.com,board.member2@example.com
# -----------------------------------------------------------------------------
# Database Configuration
# -----------------------------------------------------------------------------
# Set to False in production to prevent logging every SQL query.
DB_ECHO=False
# -----------------------------------------------------------------------------
# Email Configuration (for fastapi-mail)
# -----------------------------------------------------------------------------
# Example for a real SMTP server (e.g., SendGrid, Mailgun)
MAIL_DRIVER=smtp
MAIL_SERVER=your-smtp-server.com
MAIL_PORT=587
MAIL_STARTTLS=True
MAIL_SSL_TLS=False
MAIL_USERNAME=your-smtp-username
MAIL_PASSWORD=your-smtp-password
MAIL_FROM=noreply@your-production-domain.com
MAIL_FROM_NAME="ProjectVote"The application uses a SQLite database to store application data.
The database is stored in the data directory at the root of the project. This is achieved using a bind mount in docker-compose.yml, which maps the ./data directory on your host machine to the /app/data directory inside the backend container.
This ensures the database file (applications.db) is directly accessible on your filesystem and persists across container restarts.
Since the database file is on your host machine at data/applications.db, you can open it using any standard SQLite database tool. There is no need to connect to the running container.
This project is licensed under the MIT License - see the LICENSE file for details.

