Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 52 additions & 0 deletions .github/workflows/documentation.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
on:
push:
branches:
- main
paths:
- "docs/**"
pull_request:
branches:
- main
paths:
- "docs/**"
types: [opened, synchronize, reopened]

permissions:
contents: read
pages: write
id-token: write

concurrency:
group: "pages"
cancel-in-progress: false

jobs:
documentation:
name: "Documentation"
runs-on: ubuntu-24.04
env:
DEPLOY_TO_GITHUB_PAGES: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }}
steps:
- uses: actions/checkout@v5
- name: "Set up Python 3.12"
uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: "Install requirements"
run: "python3 -m pip install -r requirements.txt"
working-directory: "docs"
- name: "Build documentation"
run: "python3 -m mkdocs build"
working-directory: "docs"
- name: "Setup Pages"
uses: actions/configure-pages@v5
if: ${{ env.DEPLOY_TO_GITHUB_PAGES == 'true' }}
- name: Upload artifact
uses: actions/upload-pages-artifact@v3
if: ${{ env.DEPLOY_TO_GITHUB_PAGES == 'true' }}
with:
path: "docs/site"
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
if: ${{ env.DEPLOY_TO_GITHUB_PAGES == 'true' }}
180 changes: 10 additions & 170 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,172 +2,28 @@

*An open-source tournament planning application for football clubs*

[![.github/workflows/validate.yaml](https://github.com/turnierplan-NET/turnierplan.NET/actions/workflows/validate.yaml/badge.svg)](https://github.com/turnierplan-NET/turnierplan.NET/actions/workflows/validate.yaml) [![Code Smells](https://sonarcloud.io/api/project_badges/measure?project=turnierplan-NET_turnierplan.NET&metric=code_smells)](https://sonarcloud.io/summary/new_code?id=turnierplan-NET_turnierplan.NET) [![Bugs](https://sonarcloud.io/api/project_badges/measure?project=turnierplan-NET_turnierplan.NET&metric=bugs)](https://sonarcloud.io/summary/new_code?id=turnierplan-NET_turnierplan.NET) [![Lines of Code](https://sonarcloud.io/api/project_badges/measure?project=turnierplan-NET_turnierplan.NET&metric=ncloc)](https://sonarcloud.io/summary/new_code?id=turnierplan-NET_turnierplan.NET) [![Coverage](https://sonarcloud.io/api/project_badges/measure?project=turnierplan-NET_turnierplan.NET&metric=coverage)](https://sonarcloud.io/summary/new_code?id=turnierplan-NET_turnierplan.NET)
[![.github/workflows/validate.yaml](https://github.com/turnierplan-NET/turnierplan.NET/actions/workflows/validate.yaml/badge.svg)](https://github.com/turnierplan-NET/turnierplan.NET/actions/workflows/validate.yaml) [![Code Smells](https://sonarcloud.io/api/project_badges/measure?project=turnierplan-NET_turnierplan.NET&metric=code_smells)](https://sonarcloud.io/summary/new_code?id=turnierplan-NET_turnierplan.NET) [![Bugs](https://sonarcloud.io/api/project_badges/measure?project=turnierplan-NET_turnierplan.NET&metric=bugs)](https://sonarcloud.io/summary/new_code?id=turnierplan-NET_turnierplan.NET) [![Lines of Code](https://sonarcloud.io/api/project_badges/measure?project=turnierplan-NET_turnierplan.NET&metric=ncloc)](https://sonarcloud.io/summary/new_code?id=turnierplan-NET_turnierplan.NET) [![Coverage](https://sonarcloud.io/api/project_badges/measure?project=turnierplan-NET_turnierplan.NET&metric=coverage)](https://sonarcloud.io/summary/new_code?id=turnierplan-NET_turnierplan.NET) [![NuGet Version](https://img.shields.io/nuget/v/Turnierplan.Adapter)](https://www.nuget.org/packages/Turnierplan.Adapter)

## Introduction

**turnierplan.NET** is mostly written in C# using [.NET](https://dotnet.microsoft.com/). This includes the core logic, the backend API and database connection as well as all publicly visible web pages. In addition, it serves the *turnierplan.NET portal*, the client application for authenticated users, based on the [Angular](https://angular.dev/) framework. Some screenshots can be seen in the [section at the end](#screenshots).

This readme describes how to deploy the application using the pre-built containers or how to get a local development environment up and running.

> [!NOTE]
> The user interface is currently only available in German 🇩🇪

## Deployment

**turnierplan.NET** comes as a pre-built container image which can be deployed with minimal configuration. The image is available on GitHub: [ghcr.io/turnierplan-net/turnierplan](https://github.com/turnierplan-NET/turnierplan.NET/pkgs/container/turnierplan)

In the simplest case, you can configure the container to use an in-memory data store. Note that this in-memory store is only meant for quick testing and is *not stable* for production!

```shell
docker run -p 80:8080 -e Turnierplan__ApplicationUrl="http://localhost" -e Database__InMemory="true" ghcr.io/turnierplan-net/turnierplan:latest
```

A PostgreSQL database can be configured by specifying the `Database__ConnectionString` environment variable:

```shell
docker run -p 80:8080 -e Turnierplan__ApplicationUrl="http://localhost" -e Database__ConnectionString="<connection_string>" ghcr.io/turnierplan-net/turnierplan:latest
```

The credentials of the initial admin user are displayed in the container logs.

> [!CAUTION]
> In a production environment, you should immediately change the administrator password to a secure one!

### Persisting Data

To persist the **turnierplan.NET** application data, create a Docker volume mapping to the `/var/turnierplan` folder inside the container.

> [!CAUTION]
> This folder contains the JWT signing key for issued access/refresh tokens.

### Environment Variables

For a basic installation, the following environment variables *must* be set:

| Environment Variable | Description |
|-------------------------------|--------------------------------------------------------------|
| `Turnierplan__ApplicationUrl` | The URL used to access the website. |
| `Database__ConnectionString` | The PostgreSQL connection string with read/write permission. |

The following environment variables *can* be set if you want to enable specific features or modify default behavior:

| Environment Variable | Description | Default |
|-----------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------|
| `ApplicationInsights__ConnectionString` | Can be set if you wish that your instance sends telemetry data to [Azure Application Insights](https://learn.microsoft.com/en-us/azure/azure-monitor/app/app-insights-overview). | - |
| `Identity__AccessTokenLifetime` | Defines the lifetime of issued JWT access tokens. | `00:03:00` |
| `Identity__RefreshTokenLifetime` | Defines the lifetime of issued JWT refresh tokens. | `1.00:00:00` |
| `Turnierplan__InstanceName` | The instance name is displayed in the header/footer of the public pages. If not specified, `turnierplan.NET` will be shown instead. | - |
| `Turnierplan__LogoUrl` | The URL of the custom logo to be displayed in the header of the public pages. If not specified, the turnierplan.NET logo will be shown instead. | - |
| `Turnierplan__ImprintUrl` | The URL of your external imprint page if you want it to be linked on the public pages. | - |
| `Turnierplan__PrivacyUrl` | The URL of your external privacy page if you want it to be linked on the public pages. | - |

> The token lifetimes must be specified as .NET `TimeSpan` strings. For example `00:03:00` means 3 minutes or `1.00:00.00` means 1 day.

### Docker Compose Example

You can use the following docker compose file to get a complete instance running on your machine:

```yaml
services:
turnierplan.database:
image: postgres:latest
environment:
- POSTGRES_PASSWORD=P@ssw0rd
- POSTGRES_DB=turnierplan
volumes:
- turnierplan-database-data:/var/lib/postgresql/data
networks:
- turnierplan
restart: unless-stopped

turnierplan.app:
image: ghcr.io/turnierplan-net/turnierplan:latest
depends_on:
- turnierplan.database
environment:
- Turnierplan__ApplicationUrl=http://localhost
- Database__ConnectionString=Host=turnierplan.database;Database=turnierplan;Username=postgres;Password=P@ssw0rd
volumes:
- turnierplan-app-data:/var/turnierplan
networks:
- turnierplan
restart: unless-stopped
ports:
- '80:8080'

volumes:
turnierplan-database-data:
turnierplan-app-data:

networks:
turnierplan:
```
## Installation

> [!TIP]
> It is recommended to *not* use the `latest` tag. Rather, pin your docker services to a specific image version.

### Storing images in AWS S3

If you prefer to store uploaded images in an AWS S3 or S3-compatible bucket, add the following environment variables to your deployment:

| Environment Variable | Description |
|---------------------------------|--------------------------------------------------|
| `ImageStorage__Type` | The image storage type, **must** be `S3`. |
| `ImageStorage__RegionEndpoint` | The AWS region endpoint, such as `eu-central-1`. |
| `ImageStorage__ServiceUrl` | The service URL when using a non-AWS S3 bucket. |
| `ImageStorage__AccessKey` | The access key identifier. |
| `ImageStorage__AccessKeySecret` | The access key secret. |
| `ImageStorage__BucketName` | The name of the bucket. |

The access key must have permissions to create, read and delete objects.

> [!NOTE]
> The `RegionEndpoint` and `ServiceUrl` variables are *mutually exclusive*. Use the former if you are using an AWS S3 bucket and use the latter if you use a S3-compatible bucket from a third party.

### Storing images in Azure Blob Storage

If you prefer to store uploaded images in Microsoft [Azure Blob Storage](https://azure.microsoft.com/en-us/products/storage/blobs/), add the following environment variables to your deployment:

| Environment Variable | Description |
|------------------------------------|----------------------------------------------|
| `ImageStorage__Type` | The image storage type, **must** be `Azure`. |
| `ImageStorage__StorageAccountName` | The name of the storage account. |
| `ImageStorage__ContainerName` | The name of the blob container. |

By default, a [DefaultAzureCredential](https://learn.microsoft.com/en-us/dotnet/api/azure.identity.defaultazurecredential?view=azure-dotnet) will be used. Therefore, if you use Azure Managed Identities, you won't have to do any further configuration. In addition, this implementation supports two additional means of authentication listed below.

When using Entra ID based authentication, the managed identity / app registration must have permission to create/read/delete blobs in the storage account. This can be achieved by assigning the [Storage Blob Data Contributor](https://learn.microsoft.com/en-us/azure/role-based-access-control/built-in-roles/storage#storage-blob-data-contributor) role.

#### Authenticating using access key

Refer to the [documentation](https://learn.microsoft.com/en-us/azure/storage/common/storage-account-keys-manage?tabs=azure-portal) on how to view and manage the access keys.

The following environment variables must be set to enable access key authentication:

| Environment Variable | Description |
|-------------------------------|--------------------------------------------------|
| `ImageStorage__UseAccountKey` | Set to `true` to use account key authentication. |
| `ImageStorage__AccountKey` | The value of the account key. |

#### Authenticate using client secret

If you have an Entra ID app registration with the necessary permissions on the storage account, you can set the following environment variables to enable client secret authentication:

| Environment Variable | Description |
|---------------------------------|---------------------------------------------------------|
| `ImageStorage__UseClientSecret` | Set to `true` to use client credentials authentication. |
| `ImageStorage__TenantId` | The tenant id where the app registration resides. |
| `ImageStorage__ClientId` | The client id of the *app registration*. |
| `ImageStorage__ClientSecret` | The value of the client secret. |
If you want to install **turnierplan.NET** on your server, please visit the [Installation guide](https://docs.turnierplan.net/installation).

## Documentation

The developer documentation of **turnierplan.NET** is located in the `docs` directory of this repository. Information about the container images and how to build them can be found in the [README.md in the docker folder](./docker/README.md).
Visit the **turnierplan.NET** documentation using the following link: [docs.turnierplan.net](https://docs.turnierplan.net)

The documentation sources are located in the `docs` directory. See the [docs readme](docs/README.md) for further information on how to edit and build the documentation.

## Development

First, you need to install the following tools:
This section describes how to set up the development environment. First, you need to install the following tools:

- .NET 10.0 SDK
- node.js v24.x and npm
Expand All @@ -177,26 +33,10 @@ To run the application from source, follow these steps:

1. Open the `src/Turnierplan.slnx` solution and navigate to the docker compose file located under `Solution Items`. Run the `turnierplan.database` docker compose service. This will start up the PostgreSQL database for local development.
2. Navigate to the `Turnierplan.App` project and run the `Turnierplan.App` launch configuration. This will start the backend using port `45000`.
3. Open a terminal and navigate to the `src/Turnierplan.App/Client` directory. Run `npm install` to install the node dependencies. Next, you can start the client application by typing `npm run start`. Note that this will only work if the backend application has previously been run because the client app startup depends on OpenAPI files generated by the backend build process.
3. Open a terminal and navigate to the `src/Turnierplan.App/Client` directory. Run `npm install` to install the node dependencies. Next, you can start the client application by typing `npm run start`.
4. Access the client application using [http://localhost:45001](http://localhost:45001) and log in using default credentials. The user name is `admin` and the password is `P@ssw0rd`.

When running locally, the API documentation can be viewed by opening [http://localhost:45000/scalar](http://localhost:45000/scalar).

## Screenshots

Below are some screenshots of the application:

1. *Creating a new tournament:*
![Screenshot that shows the create tournament page](images/create-tournament.png)
2. *Exporting a tournament to PDF document:*
![Screenshot that shows the PDF export page](images/export-pdf.png)
3. *Viewing a timetable of multiple tournaments:*
![Screenshot that shows the folder timetable page](images/folder-timetable.png)
4. *Reporting the outcome of a match:*
![Screenshow that shows the match reporting dialog](images/report-match.png)

## Turnierplan.Adapter

If you want to use the **turnierplan.NET** API programatically in a .NET environment, you can use the `Turnierplan.Adapter` [NuGet package](https://www.nuget.org/packages/Turnierplan.Adapter) which contains all model classes and an abstraction layer to easily query the API endpoints.

Please refer to the [package readme](src/Turnierplan.Adapter/README.md) for details and usage examples.
> [!NOTE]
> The solution must be built first before the client application can be started. This is because the client application startup depends on OpenAPI files generated during the solution build.
1 change: 1 addition & 0 deletions docs/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
site
16 changes: 16 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
## turnierplan.NET &middot; Documentation

This directory contains the source markdown files and mkdocs configuration for the publically available turnierplan.NET documentation: [https://docs.turnierplan.net](https://docs.turnierplan.net).

In order to build the documentation locally, you must first install Python and [mkdocs](https://www.mkdocs.org):

```
pip install -r requirements.txt
```

Next, you can either view the rendered documentation using the mkdocs-build-in server or you can generate the static website files:

```
python3 -m mkdocs serve # starts a local web server on port 8000
python3 -m mkdocs build # generates static web site artifacts into the 'site' directory
```
10 changes: 0 additions & 10 deletions docs/image-credit.md

This file was deleted.

32 changes: 32 additions & 0 deletions docs/mkdocs.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
site_name: "turnierplan.NET"
site_author: "turnierplan.NET"
site_url: "https://docs.turnierplan.net"
site_description: "The administrator and user documentation for turnierplan.NET"

repo_url: "https://github.com/turnierplan-NET/turnierplan.NET"
edit_uri: "blob/main/docs/pages/"

copyright: "Copyright &copy; 2026 Elias Hörner"

docs_dir: "pages"

theme:
name: mkdocs
color_mode: auto
user_color_mode_toggle: true
nav_style: dark
navigation_depth: 4
locale: en

nav:
- Home: "index.md"
- Installation: "installation.md"
- About:
- Releases: "https://github.com/turnierplan-NET/turnierplan.NET/releases"
- License: "https://github.com/turnierplan-NET/turnierplan.NET/blob/main/LICENSE"

extra_css:
- "assets/turnierplan.css"

markdown_extensions:
- admonition:
46 changes: 46 additions & 0 deletions docs/pages/assets/turnierplan.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
* Some style improvements for the footer
*/

footer hr {
opacity: 0.2;
}

footer p {
font-size: 0.8em;
margin-bottom: 0.3em;
}

/*
* Remove the sidebar on the homepage, copied from the mkdocs.org documentation
* => https://github.com/mkdocs/mkdocs/blob/2862536793b3c67d9d83c33e0dd6d50a791928f8/docs/css/extra.css#L48
*/

body.homepage > div.container > div.row > div.col-md-3 {
display: none;
}

body.homepage > div.container > div.row > div.col-md-9 {
margin-left: 0;
flex: 0 0 100%;
max-width: 100%;
}
/*
* Some additional style changes for the homepage
*/

body.homepage h1 {
margin-top: 2em;
margin-bottom: 0.7em;
text-align: center;
font-weight: bold;
}

body.homepage > div.container p:first-of-type {
text-align: center;
}

body.homepage > div.container img:first-of-type {
padding: unset;
border: unset;
}
Binary file added docs/pages/img/favicon.ico
Binary file not shown.
Binary file added docs/pages/img/logo-192.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading