add smtp mailer settings #470
16 changed files with 9624 additions and 9715 deletions
43
.drone.yml
43
.drone.yml
|
|
@ -219,24 +219,8 @@ trigger:
|
|||
- main
|
||||
event:
|
||||
- push
|
||||
- tag
|
||||
|
||||
steps:
|
||||
- name: build-and-publish-container
|
||||
image: plugins/docker
|
||||
settings:
|
||||
registry: git.local-it.org
|
||||
repo: git.local-it.org/local-it/mitgliederverwaltung
|
||||
username:
|
||||
from_secret: DRONE_REGISTRY_USERNAME
|
||||
password:
|
||||
from_secret: DRONE_REGISTRY_TOKEN
|
||||
auto_tag: true
|
||||
auto_tag_suffix: ${DRONE_COMMIT_SHA:0:8}
|
||||
when:
|
||||
event:
|
||||
- tag
|
||||
|
||||
- name: build-and-publish-container-branch
|
||||
image: plugins/docker
|
||||
settings:
|
||||
|
|
@ -256,6 +240,33 @@ steps:
|
|||
depends_on:
|
||||
- check-fast
|
||||
|
||||
---
|
||||
kind: pipeline
|
||||
type: docker
|
||||
name: build-and-release
|
||||
|
||||
trigger:
|
||||
event:
|
||||
- tag
|
||||
|
||||
steps:
|
||||
- name: build-and-publish-container
|
||||
image: plugins/docker
|
||||
settings:
|
||||
registry: git.local-it.org
|
||||
repo: git.local-it.org/local-it/mitgliederverwaltung
|
||||
username:
|
||||
from_secret: DRONE_REGISTRY_USERNAME
|
||||
password:
|
||||
from_secret: DRONE_REGISTRY_TOKEN
|
||||
auto_tag: true
|
||||
when:
|
||||
event:
|
||||
- tag
|
||||
|
||||
depends_on:
|
||||
- check-fast
|
||||
|
||||
---
|
||||
kind: pipeline
|
||||
type: docker
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ We are building a membership management system (Mila) using the following techno
|
|||
8. [Accessibility Guidelines](#8-accessibility-guidelines)
|
||||
|
||||
**Related documents:**
|
||||
- **UI / UX:** [`DESIGN_DUIDELINES.md`](../DESIGN_DUIDELINES.md) defines visual and interaction consistency: use of CoreComponents (no raw DaisyUI in views), page skeleton (`<.header>`, `mt-6 space-y-6`), **Back button left in header for edit/new forms** (§2.2), typography, buttons, forms, tables, flash/toast, and microcopy (e.g. German "du" and glossary). Follow "components first" and semantic variants instead of hard-coded colors.
|
||||
- **UI / UX:** [`DESIGN_GUIDELINES.md`](../DESIGN_GUIDELINES.md) defines visual and interaction consistency: use of CoreComponents (no raw DaisyUI in views), page skeleton (`<.header>`, `mt-6 space-y-6`), **Back button left in header for edit/new forms** (§2.2), typography, buttons, forms, tables, flash/toast, and microcopy (e.g. German "du" and glossary). Follow "components first" and semantic variants instead of hard-coded colors.
|
||||
- **Vereinfacht API:** [`docs/vereinfacht-api.md`](docs/vereinfacht-api.md) describes the finance-contact sync (find by email filter, minimal create payload, no extra required member fields).
|
||||
|
||||
---
|
||||
|
|
@ -1339,13 +1339,16 @@ dgettext("auth", "Sign in with email")
|
|||
**Extract and Merge:**
|
||||
|
||||
```bash
|
||||
# Extract new translatable strings
|
||||
mix gettext.extract
|
||||
# Extract new translatable strings and merge into existing .po files (recommended)
|
||||
mix gettext.extract --merge
|
||||
|
||||
# Merge into existing translations
|
||||
# Alternative: extract only, then merge separately
|
||||
mix gettext.extract
|
||||
mix gettext.merge priv/gettext --on-obsolete=mark_as_obsolete
|
||||
```
|
||||
|
||||
**Gettext merge workflow:** Prefer `mix gettext.extract --merge` so the `.pot` template is regenerated from source and merged into all locale `.po` files in one step. Edit only the `msgstr` values in `.po` files for translations; do not manually change source references, entry order, or the `.pot` file structure. If Git merge conflicts appear in `.po` or `.pot` files, resolve by removing conflict markers (keeping both sides where appropriate), then run `mix gettext.extract --merge`. If the `.pot` file is corrupted, delete it and run `mix gettext.extract --merge` to regenerate it from source.
|
||||
|
||||
### 3.13 Task Runner: Just
|
||||
|
||||
**Common Commands:**
|
||||
|
|
|
|||
54
README.md
54
README.md
|
|
@ -2,12 +2,12 @@
|
|||
|
||||
**Mila** — simple, usable, self-hostable membership management for small to mid-sized clubs.
|
||||
|
||||
[](https://drone.dev.local-it.cloud/local-it/mitgliederverwaltung)
|
||||
[](https://drone.cicd.local-it.cloud/local-it/mitgliederverwaltung)
|
||||

|
||||
|
||||
## 🚧 Project Status
|
||||
|
||||
⚠️ **Early development** — not production-ready. Expect breaking changes.
|
||||
⚠️ **First Version** — Expect breaking changes.
|
||||
Contributions and feedback are welcome!
|
||||
|
||||
## ✨ Overview
|
||||
|
|
@ -48,9 +48,10 @@ You can find our documentation for users here: https://wiki.local-it.org/s/mila-
|
|||
- ✅ SSO via OIDC (works with Authentik, Rauthy, Keycloak, etc.)
|
||||
- ✅ Sidebar navigation (standard-compliant, accessible)
|
||||
- ✅ Global settings management
|
||||
- 🚧 Self-service & online application
|
||||
- ✅ Self-service & online application
|
||||
- ✅ Accessibility improvements (WCAG 2.1 AA compliant keyboard navigation)
|
||||
- 🚧 Email sending
|
||||
- ✅ Email sending
|
||||
- ✅ Integration of Accounting-Software ([Vereinfacht](https://github.com/vereinfacht/vereinfacht))
|
||||
|
||||
## 🚀 Quick Start (Development)
|
||||
|
||||
|
|
@ -173,12 +174,6 @@ The `OIDC_REDIRECT_URI` is auto-generated as `https://{DOMAIN}/auth/user/oidc/ca
|
|||
## ⚙️ 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
|
||||
|
||||
|
|
@ -193,6 +188,8 @@ The `OIDC_REDIRECT_URI` is auto-generated as `https://{DOMAIN}/auth/user/oidc/ca
|
|||
- `lib/mv_web/` — Phoenix controllers, LiveViews, components
|
||||
- `lib/mv/` — Shared helpers and business logic
|
||||
- `assets/` — Tailwind, JavaScript, static files
|
||||
- `test/` — All tests
|
||||
|
||||
|
||||
📚 **Full tech stack details:** See [`CODE_GUIDELINES.md`](CODE_GUIDELINES.md)
|
||||
📖 **Implementation history:** See [`docs/development-progress-log.md`](docs/development-progress-log.md)
|
||||
|
|
@ -228,42 +225,19 @@ For testing the production Docker build locally:
|
|||
# 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:**
|
||||
3. **Start production environment:**
|
||||
```bash
|
||||
docker compose -f docker-compose.prod.yml up
|
||||
```
|
||||
|
||||
5. **Database migrations run automatically** on app start. For manual migration:
|
||||
4. **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:**
|
||||
5. **Access the production app:**
|
||||
- Production App: http://localhost:4001
|
||||
- Uses same Rauthy instance as dev (localhost:8080)
|
||||
|
||||
|
|
@ -286,9 +260,9 @@ For actual production deployment:
|
|||
## 🤝 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.
|
||||
- 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
|
||||
|
||||
|
|
@ -298,4 +272,4 @@ See the [LICENSE](LICENSE) file for details.
|
|||
## 📬 Contact
|
||||
|
||||
- Issues: [GitLab Issues](https://git.local-it.org/local-it/mitgliederverwaltung/-/issues)
|
||||
- Community links: coming soon.
|
||||
- E-Mail: info@local-it.org
|
||||
|
|
|
|||
|
|
@ -195,7 +195,7 @@
|
|||
- Auto-dismiss: info/success 4–6s, warning 6–8s, error 8–12s; dismiss button kept for accessibility.
|
||||
- Implement via JS hook (e.g. `FlashAutoDismiss`) + `data-dismiss-ms` (or `data-kind`) on flash component; on timeout push `lv:clear-flash` and hide element.
|
||||
- LiveView: add shared `handle_event("lv:clear-flash", %{"key" => key}, socket)` (e.g. in `MvWeb` live_view quote) calling `clear_flash(socket, key)`.
|
||||
- All flashes (including “Email copied”) use the same variants (info, success, warning, error); no special tone. See `DESIGN_DUIDELINES.md` §9.
|
||||
- All flashes (including “Email copied”) use the same variants (info, success, warning, error); no special tone. See `DESIGN_GUIDELINES.md` §9.
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -442,7 +442,7 @@ defmodule MvWeb.GlobalSettingsLive do
|
|||
</.form_section>
|
||||
|
||||
<%!-- Vereinfacht Integration Section --%>
|
||||
<.form_section title={gettext("Vereinfacht Integration")}>
|
||||
<.form_section title={gettext("Accounting-Software (Vereinfacht) Integration")}>
|
||||
<%= if @vereinfacht_env_configured do %>
|
||||
<p class="text-sm text-base-content/70 mb-4">
|
||||
{gettext("Some values are set via environment variables. Those fields are read-only.")}
|
||||
|
|
@ -550,7 +550,7 @@ defmodule MvWeb.GlobalSettingsLive do
|
|||
</.form>
|
||||
</.form_section>
|
||||
<%!-- OIDC Section --%>
|
||||
<.form_section title={gettext("OIDC")}>
|
||||
<.form_section title={gettext("OIDC (Single Sign-On)")}>
|
||||
<%= if @oidc_env_configured do %>
|
||||
<p class="text-sm text-base-content/70 mb-4">
|
||||
{gettext("Some values are set via environment variables. Those fields are read-only.")}
|
||||
|
|
|
|||
|
|
@ -28,15 +28,6 @@ defmodule MvWeb.ImportLive.Components do
|
|||
"Use the data field name as the CSV column header in your file. Data fields must exist in Mila before importing, because unknown data field columns will be ignored. Groups and membership fees are not supported for import."
|
||||
)}
|
||||
</p>
|
||||
<p class="text-sm">
|
||||
<.link
|
||||
href={~p"/settings#custom_fields"}
|
||||
class="link"
|
||||
data-testid="custom-fields-link"
|
||||
>
|
||||
{gettext("Manage Member Data")}
|
||||
</.link>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -230,7 +230,7 @@ defmodule MvWeb.MemberLive.Show do
|
|||
<%!-- Custom Fields Section --%>
|
||||
<%= if Enum.any?(@custom_fields) do %>
|
||||
<div>
|
||||
<.section_box title={gettext("Custom Fields")}>
|
||||
<.section_box title={gettext("Individual datafields")}>
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<%= for custom_field <- @custom_fields do %>
|
||||
<% cfv = find_custom_field_value(@member.custom_field_values, custom_field.id) %>
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
<.header>
|
||||
{gettext("Listing Roles")}
|
||||
<:subtitle>
|
||||
{gettext("Manage user roles and their permission sets.")}
|
||||
{gettext("Manage roles and their permission sets.")}
|
||||
</:subtitle>
|
||||
<:actions>
|
||||
<%= if can?(@current_user, :create, Mv.Authorization.Role) do %>
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -78,7 +78,7 @@ defmodule MvWeb.Components.MemberFilterComponentTest do
|
|||
html = render(view)
|
||||
# Should have both "Payments" and "Custom Fields" group labels
|
||||
assert html =~ gettext("Payments") || html =~ "Payment"
|
||||
assert html =~ gettext("Custom Fields")
|
||||
assert html =~ gettext("Individual datafields")
|
||||
end
|
||||
|
||||
test "renders only payment filter when no boolean custom fields exist", %{conn: conn} do
|
||||
|
|
|
|||
|
|
@ -77,7 +77,7 @@ defmodule MvWeb.CustomFieldLive.DeletionTest do
|
|||
|
||||
# Edit mode: section titles must not reappear when modal opens (regression)
|
||||
refute has_element?(view, "h2", "Member fields")
|
||||
refute has_element?(view, "h2", "Custom fields")
|
||||
refute has_element?(view, "h2", "Individual datafields")
|
||||
|
||||
# Should show correct member count (1 member)
|
||||
assert render(view) =~ "1 member has a value assigned for this datafield"
|
||||
|
|
|
|||
|
|
@ -238,7 +238,6 @@ defmodule MvWeb.ImportLiveTest do
|
|||
assert has_element?(view, "[data-testid='import-page']")
|
||||
assert has_element?(view, "[data-testid='csv-upload-form']")
|
||||
assert has_element?(view, "[data-testid='start-import-button']")
|
||||
assert has_element?(view, "[data-testid='custom-fields-link']")
|
||||
end
|
||||
|
||||
test "template links and file input are present", %{conn: conn} do
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@ defmodule MvWeb.MemberLive.ShowTest do
|
|||
{:ok, _view, html} = live(conn, ~p"/members/#{member}")
|
||||
|
||||
# Custom Fields section should be visible
|
||||
assert html =~ gettext("Custom Fields")
|
||||
assert html =~ gettext("Individual datafields")
|
||||
|
||||
# Custom field label should be visible
|
||||
assert html =~ custom_field.name
|
||||
|
|
@ -97,7 +97,7 @@ defmodule MvWeb.MemberLive.ShowTest do
|
|||
{:ok, _view, html} = live(conn, ~p"/members/#{member}")
|
||||
|
||||
# Custom Fields section should be visible
|
||||
assert html =~ gettext("Custom Fields")
|
||||
assert html =~ gettext("Individual datafields")
|
||||
|
||||
# Both field labels should be visible
|
||||
assert html =~ field1.name
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue