# Mila

**Mila** — simple, usable, self-hostable membership management for small to mid-sized clubs.  

[![Build Status](https://drone.dev.local-it.cloud/api/badges/local-it/mitgliederverwaltung/status.svg)](https://drone.dev.local-it.cloud/local-it/mitgliederverwaltung)
![License](https://img.shields.io/badge/license-AGPL--v3-blue)  

## 🚧 Project Status

⚠️ **Early development** — not production-ready. Expect breaking changes.  
Contributions and feedback are welcome!

## ✨ Overview

Mila is a free and open-source membership management tool designed for real club needs.  
It is **self-hosting friendly**, aims for **accessibility and GDPR compliance**, and focuses on **usability** instead of feature overload.  

## 💡 Why Mila?

Most membership tools for clubs are either:

* **Too complex** — overloaded with features small and mid-sized clubs don’t need
* **Too expensive** — hidden fees, closed ecosystems, vendor lock-in
* **Too rigid** — no way to adapt fields, processes, or roles to your club’s reality

**Mila** is different:

* **Simple**: Focused on what clubs really need — members, dues, communication.
* **Usable**: Clean, accessible UI, GDPR-compliant, designed with everyday volunteers in mind.
* **Flexible**: Customize what data you collect about members, role-based permissions, and self-service for members.
* **Truly open**: 100% free and open source — no lock-in, transparent code, self-host friendly.

Our philosophy: **software should help people spend less time on administration and more time on their community.**

## 📸 Screenshots

![Screenshot placeholder](docs/images/screenshot.png)  
*This is how Mila might look in action.*  

## 🔑 Features

- ✅ Manage member data with ease  
- ✅ Membership fees & payment status tracking  
- ✅ Full-text search with fuzzy matching  
- ✅ Sorting & filtering  
- ✅ Roles & permissions (RBAC system with 4 permission sets)  
- ✅ Custom fields (flexible per club needs)  
- ✅ SSO via OIDC (works with Authentik, Rauthy, Keycloak, etc.)  
- ✅ Sidebar navigation (standard-compliant, accessible)  
- ✅ Global settings management  
- 🚧 Self-service & online application  
- ✅ Accessibility improvements (WCAG 2.1 AA compliant keyboard navigation)  
- 🚧 Email sending  

## 🚀 Quick Start (Development)

### Prerequisites

We recommend using **[asdf](https://asdf-vm.com/)** for managing tool versions.  
- Tested with: `asdf 0.16.5`
- Required versions are documented in `.tool-versions` in this repo  

<details>
<summary>Install system dependencies (Debian/Ubuntu)</summary>

```bash
# Debian 12
apt-get -y install build-essential autoconf m4 libncurses-dev libwxgtk3.2-dev libwxgtk-webview3.2-dev libgl1-mesa-dev libglu1-mesa-dev libpng-dev libssh-dev unixodbc-dev xsltproc fop libxml2-utils openjdk-17-jdk icu-devtools bison flex pkg-config

# Ubuntu 24
apt-get -y install build-essential autoconf m4 libwxgtk3.2-dev libwxgtk-webview3.2-dev libgl1-mesa-dev libglu1-mesa-dev libpng-dev libssh-dev unixodbc-dev xsltproc fop libxml2-utils libncurses-dev openjdk-11-jdk icu-devtools bison flex libreadline-dev
```
</details>

<details>
<summary>Install asdf</summary>

```bash
mkdir ~/.asdf
cd ~/.asdf
wget https://github.com/asdf-vm/asdf/releases/download/v0.16.5/asdf-v0.16.5-linux-amd64.tar.gz
tar -xvf asdf-v0.16.5-linux-amd64.tar.gz
ln -s ~/.asdf/asdf ~/.local/bin/asdf
```

Then follow the official “Shell Configuration” steps in the asdf docs.  

*Fish example* (`~/.config/fish/config.fish`):  

```fish
asdf completion fish > ~/.config/fish/completions/asdf.fish
set -gx PATH "$HOME/.asdf/shims" $PATH
```

*Bash example* (`~/.bash_profile` and `~/.bashrc`):  

```bash
export PATH="${ASDF_DATA_DIR:-$HOME/.asdf}/shims:$PATH"
. <(asdf completion bash)
```
</details>

### Install project dependencies

```bash
git clone https://git.local-it.org/local-it/mitgliederverwaltung.git mila
cd mila
asdf install

# Inside the repo folder:
mix local.hex 
mix archive.install hex phx_new 
```

> Note: running `mix local.hex` must be done inside the repo folder,  
> because `.tool-versions` defines the Erlang/Elixir versions.

### Run the app

1. Copy env file:  
   ```bash
   cp .env.example .env
   # Set OIDC_CLIENT_SECRET inside .env
   ```

2. Start everything (database, Mailcrab, Rauthy, app):  
   ```bash
   just run
   ```

3. Services will be available at:  
   - App: <http://localhost:4000>  
   - Mail UI: <http://localhost:1080>  
   - Postgres: `localhost:5000`  

## 🔐 Testing SSO locally

Mila uses OIDC for Single Sign-On. In development, a local **Rauthy** instance is provided.  

1. `just run`
2. go to [localhost:8080](http://localhost:8080), go to the Admin area
3. Login with "admin@localhost" and password from `BOOTSTRAP_ADMIN_PASSWORD_PLAIN` in docker-compose.yml
4. add client from the admin panel
    - Client ID: mv
    - redirect uris: http://localhost:4000/auth/user/oidc/callback
    - Authorization Flows: authorization_code
    - allowed origins: http://localhost:4000
    - access/id token algortihm: RS256 (EDDSA did not work for me, found just few infos in the ashauthentication docs)
5. copy client secret to `.env` file
6. abort and run `just run` again

Now you can log in to Mila via OIDC!

### OIDC with other providers (Authentik, Keycloak, etc.)

Mila works with any OIDC-compliant provider. The internal strategy is named `:oidc` — it works with any OIDC-compliant provider.

**Important:** The redirect URI must always end with `/auth/user/oidc/callback`.

Example for Authentik:
1. Create an OAuth2/OpenID Provider in Authentik
2. Set the redirect URI to: `https://your-domain.com/auth/user/oidc/callback`
3. Configure environment variables:
   ```bash
   DOMAIN=your-domain.com                    # or PHX_HOST=your-domain.com
   OIDC_CLIENT_ID=your-client-id
   OIDC_BASE_URL=https://auth.example.com/application/o/your-app
   OIDC_CLIENT_SECRET=your-client-secret     # or use OIDC_CLIENT_SECRET_FILE
   ```

The `OIDC_REDIRECT_URI` is auto-generated as `https://{DOMAIN}/auth/user/oidc/callback` if not explicitly set.  

## ⚙️ Configuration

- **Env vars:** see `.env.example`  
  - `OIDC_CLIENT_SECRET` — secret for your OIDC client  
- Database defaults (Docker Compose):  
  - Host: `localhost`  
  - Port: `5000`  
  - User/pass: `postgres` / `postgres`  
  - DB: `mila_dev`  

## 🏗️ Architecture

**Tech Stack Overview:**
- **Backend:** Elixir + Phoenix + Ash Framework  
- **Frontend:** Phoenix LiveView + Tailwind CSS + DaisyUI  
- **Database:** PostgreSQL  
- **Auth:** AshAuthentication (OIDC + password)

**Code Structure:**
- `lib/accounts/` & `lib/membership/` & `lib/membership_fees/` & `lib/mv/authorization/` — Ash resources and domains
- `lib/mv_web/` — Phoenix controllers, LiveViews, components  
- `lib/mv/` — Shared helpers and business logic
- `assets/` — Tailwind, JavaScript, static files

📚 **Full tech stack details:** See [`CODE_GUIDELINES.md`](CODE_GUIDELINES.md)  
📖 **Implementation history:** See [`docs/development-progress-log.md`](docs/development-progress-log.md)  
🗄️ **Database schema:** See [`docs/database-schema-readme.md`](docs/database-schema-readme.md)  

## 🧑‍💻 Development

**Common commands:**
```bash
just run                      # Start full dev environment
just test                     # Run test suite
just lint                     # Code style checks
just audit                    # Security audits
just reset-database           # Reset local DB
```

📚 **Full development guidelines:** See [`CODE_GUIDELINES.md`](CODE_GUIDELINES.md)  

## 📦 Production Deployment

### Local Production Testing

For testing the production Docker build locally:

1. **Generate secrets:**
   ```bash
   mix phx.gen.secret  # for SECRET_KEY_BASE
   mix phx.gen.secret  # for TOKEN_SIGNING_SECRET
   ```

2. **Create `.env` file:**
   ```bash
   # Copy template and edit
   cp .env.example .env
   nano .env
   
   # Required variables:
   SECRET_KEY_BASE=<your-generated-secret>
   TOKEN_SIGNING_SECRET=<your-generated-secret>
   DOMAIN=localhost                          # or PHX_HOST=localhost
   
   # Optional OIDC configuration:
   # OIDC_CLIENT_ID=mv
   # OIDC_BASE_URL=http://localhost:8080/auth/v1
   # OIDC_CLIENT_SECRET=<from-your-oidc-provider>
   # OIDC_REDIRECT_URI is auto-generated as https://{DOMAIN}/auth/user/oidc/callback
   
   # Alternative: Use _FILE variables for Docker secrets (takes priority over regular vars):
   # SECRET_KEY_BASE_FILE=/run/secrets/secret_key_base
   # TOKEN_SIGNING_SECRET_FILE=/run/secrets/token_signing_secret
   # OIDC_CLIENT_SECRET_FILE=/run/secrets/oidc_client_secret
   # DATABASE_URL_FILE=/run/secrets/database_url
   # DATABASE_PASSWORD_FILE=/run/secrets/database_password
   ```

3. **Start development environment** (for Rauthy):
   ```bash
   docker compose up -d
   ```

4. **Start production environment:**
   ```bash
   docker compose -f docker-compose.prod.yml up
   ```

5. **Database migrations run automatically** on app start. For manual migration:
   ```bash
   docker compose -f docker-compose.prod.yml exec app /app/bin/mv eval "Mv.Release.migrate"
   ```

6. **Access the production app:**
   - Production App: http://localhost:4001
   - Uses same Rauthy instance as dev (localhost:8080)

**Note:** The local production setup uses `network_mode: host` to share localhost with the development Rauthy instance. For real production deployment, configure an external OIDC provider and remove `network_mode: host`.

### Real Production Deployment

For actual production deployment:

1. **Use an external OIDC provider** (not the local Rauthy)
2. **Update `docker-compose.prod.yml`:**
   - Remove `network_mode: host`
   - Set `OIDC_BASE_URL` to your production OIDC provider
   - Configure proper Docker networks
3. **Set up SSL/TLS** (e.g., via reverse proxy like Nginx/Traefik)
4. **Use secure secrets management** — All sensitive environment variables support a `_FILE` suffix for Docker secrets (e.g., `SECRET_KEY_BASE_FILE=/run/secrets/secret_key_base`). See `docker-compose.prod.yml` for an example setup with Docker secrets.
5. **Configure database backups**


## 🤝 Contributing

We welcome contributions!  
- Open issues and PRs in this repo.  
- Please follow existing code style and conventions.  
- Expect breaking changes while the project is in early development.  

## 📄 License

**License: AGPLv3**  
See the [LICENSE](LICENSE) file for details.  

## 📬 Contact

- Issues: [GitLab Issues](https://git.local-it.org/local-it/mitgliederverwaltung/-/issues)  
- Community links: coming soon.  
