Skip to content
Open
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
36 changes: 0 additions & 36 deletions .docker/office/Dockerfile

This file was deleted.

5 changes: 4 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,10 @@ registration_worker_sleep_time=10
# Shipment worker settings
shipment_worker_sleep_time=10
sender_label=Something Corp
path_to_libreoffice=somewhere/LibreOffice/program/soffice.exe

# Message broker settings
message_broker_queue_id=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
message_broker_worker_sleep_time=60

# OIDC settings
client_id=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
Expand Down
3 changes: 2 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
"cSpell.words": [
"fastapi"
"fastapi",
"gotenberg"
],
"search.useIgnoreFiles": true
}
60 changes: 17 additions & 43 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,29 +3,24 @@
## Introduction

OpenPostbud is a web application that makes it possible to do mail merge and mass shipment of Digital Post
using Service Platformen.
using Kombit's Serviceplatformen.

OpenPostbud is split into two logical parts: The web app and the task workers.
The web app is the frontend presented to the user. The workers run in separate processes
performing any queued up shipment or registration tasks.

## Installation

OpenPostbud needs Python (>=3.11), pip and setuptools installed to be installed.
OpenPostbud comes with a Docker compose file that will install and start all necessary processes.

To install OpenPostbud navigate to the main folder where pyproject.toml is located and call

```bash
pip install .
```

This will build and install OpenPostbud into the active environment.
It will also install any dependencies from pip.
Remember to create a `.env` file with all needed environment variables.
See below for explanations as well as `.env.example`.

### Libre Office

OpenPostbud uses Libre Office to convert docx files to pdf.
It does so by calling the Libre Office executable in the command line.
This is automatically installed by Docker.

## Environment variables

Expand All @@ -49,17 +44,18 @@ OpenPostbud needs the following environment variables set:

### Workers

The shipment and registration workers need the following environment variables set:
The shipment, registration and message broker workers need the following environment variables set:

| Name | description | Type |
|--------------------------------|-----------------------------------------------------------------------|-------------|
| cvr | The CVR number of the organisation | String |
| kombit_cert_path | The absolute path to the certificate file used for Service Platformen | Path string |
| Kombit_test_env | Whether to use the test environment of Service Platformen | boolean |
| registration_worker_sleep_time | The number of seconds for the registration worker to idle | Integer |
| shipment_worker_sleep_time | The number of seconds for the shipment worker to idle | Integer |
| sender_label | The label to set on the sender of Digital Post | String |
| path_to_libreoffice | The absolute path to the Libre Office executable | Path string |
| Name | description | Type |
| -------------------------------- | ------------------------------------------------------------------------- | ----------- |
| cvr | The CVR number of the organisation | String |
| kombit_cert_path | The absolute path to the certificate file used for Service Platformen | Path string |
| Kombit_test_env | Whether to use the test environment of Service Platformen | boolean |
| registration_worker_sleep_time | The number of seconds for the registration worker to idle | Integer |
| shipment_worker_sleep_time | The number of seconds for the shipment worker to idle | Integer |
| sender_label | The label to set on the sender of Digital Post | String |
| message_broker_queue_id | The UUID of the message broker queue. Get this from the Kombit admin page | UUID |
| message_broker_worker_sleep_time | The number of seconds for the message broker worker to idle | Integer |

### Development

Expand All @@ -75,28 +71,6 @@ Under development it's possible to set some environment variables to help with t
OpenPostbud adds a command line executable called `OpenPostbud`.
Use `OpenPostbud -h` to see help information about the CLI.

### OpenPostbud app

To run the app execute the following command:

```bash
OpenPostbud run
```

This will start a Uvicorn server which listens on port 8000.

### Workers

To run the workers:

```bash
OpenPostbud registration_worker
OpenPostbud shipment_worker
```

These workers will run in an infinite loop where they check the database for tasks. If there are no tasks the
workers will sleep for a set amount of time.

## Database

OpenPostbud uses SQLite and it creates an SQLite database in the current working directory called `database.db`.
Expand All @@ -105,6 +79,6 @@ Some sensitive columns in the database are encrypted using AES.

## Authentication

The OpenPostbud web app uses Microsoft OIDC to authenticate users. This needs to be set up in the Microsoft Entra.
The OpenPostbud web app uses Microsoft OIDC to authenticate users. This needs to be set up in Microsoft Entra.

Admins can use the CLI command `OpenPostbud admin_access` to get a single-use login URL.
2 changes: 0 additions & 2 deletions certs/.gitignore

This file was deleted.

23 changes: 19 additions & 4 deletions docker-compose.server.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ services:
dockerfile: .docker/app/Dockerfile
command: "python src/OpenPostbud/main.py"
restart: unless-stopped
depends_on:
- gotenberg
networks:
- app
volumes:
Expand All @@ -57,7 +59,7 @@ services:
restart: unless-stopped
depends_on:
- app
- office
- gotenberg
networks:
- app
volumes:
Expand All @@ -82,11 +84,24 @@ services:
environment:
TZ: Europe/Copenhagen

office:
message_broker_worker:
build:
context: .
dockerfile: .docker/office/Dockerfile
command: "python src/OpenPostbud/workers/pdf_converter.py"
dockerfile: .docker/app/Dockerfile
command: "python src/OpenPostbud/workers/message_broker_worker.py"
restart: unless-stopped
depends_on:
- app
networks:
- app
volumes:
- ./:/app
- ./certs:/app/certs:ro
environment:
TZ: Europe/Copenhagen

gotenberg:
image: gotenberg/gotenberg:8
restart: unless-stopped
networks:
- app
Expand Down
25 changes: 20 additions & 5 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ services:
- app
- shipment_worker
- registration_worker
- office
- gotenberg
ports:
- '8080'
volumes:
Expand All @@ -39,6 +39,8 @@ services:
dockerfile: .docker/app/Dockerfile
command: "python src/OpenPostbud/main.py"
restart: unless-stopped
depends_on:
- gotenberg
networks:
- app
volumes:
Expand All @@ -55,7 +57,7 @@ services:
restart: unless-stopped
depends_on:
- app
- office
- gotenberg
networks:
- app
volumes:
Expand All @@ -80,11 +82,24 @@ services:
environment:
TZ: Europe/Copenhagen

office:
message_broker_worker:
build:
context: .
dockerfile: .docker/office/Dockerfile
command: "python src/OpenPostbud/workers/pdf_converter.py"
dockerfile: .docker/app/Dockerfile
command: "python src/OpenPostbud/workers/message_broker_worker.py"
restart: unless-stopped
depends_on:
- app
networks:
- app
volumes:
- ./:/app
- ./certs:/app/certs:ro
environment:
TZ: Europe/Copenhagen

gotenberg:
image: gotenberg/gotenberg:8
restart: unless-stopped
networks:
- app
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ dependencies = [
"nicegui == 2.*",
"python-dotenv == 1.*",
"docx-mailmerge2 == 0.8.2",
"python_serviceplatformen == 3.*",
"python_serviceplatformen >= 3.1, < 4.0",
"passlib == 1.7.*",
"PyJWT == 2.10.*"
]
Expand Down
29 changes: 0 additions & 29 deletions src/OpenPostbud/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@

import argparse

import OpenPostbud.main
from OpenPostbud.middleware import authentication
from OpenPostbud.workers import registration_worker, shipment_worker


# pylint: disable=unused-argument
Expand All @@ -13,24 +11,6 @@ def admin_access_command(args: argparse.Namespace):
authentication.grant_admin_access()


# pylint: disable=unused-argument
def run_command(args: argparse.Namespace):
"""The command to run on the 'run' subcommand."""
OpenPostbud.main.main(reload=False)


# pylint: disable=unused-argument
def r_worker_command(args: argparse.Namespace):
"""The command to run on the 'registration_worker' subcommand."""
registration_worker.start_process()


# pylint: disable=unused-argument
def s_worker_command(args: argparse.Namespace):
"""The command to run on the 'shipment_worker' subcommand."""
shipment_worker.start_process()


def main():
"""Main entry point for the CLI."""
parser = argparse.ArgumentParser(
Expand All @@ -43,15 +23,6 @@ def main():
admin_parser = subparsers.add_parser("admin_access", help="Generate a single-use admin URL to the web app.")
admin_parser.set_defaults(func=admin_access_command)

run_parser = subparsers.add_parser("run", help="Run the web application.")
run_parser.set_defaults(func=run_command)

r_worker_parser = subparsers.add_parser("registration_worker", help="Start the registration worker.")
r_worker_parser.set_defaults(func=r_worker_command)

s_worker_parser = subparsers.add_parser("shipment_worker", help="Start the shipment worker.")
s_worker_parser.set_defaults(func=s_worker_command)

args = parser.parse_args()
args.func(args)

Expand Down
9 changes: 8 additions & 1 deletion src/OpenPostbud/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,10 @@ def str_to_bool(s: str) -> bool:
return s.lower() == "true"


# Set logging options for all processes
logging.basicConfig(level=logging.INFO, format="%(levelname)s | %(asctime)s | %(message)s", datefmt="%m/%d/%Y %H:%M:%S%z")

# Load .env file
ENV_PATH = ".env"

if not os.path.isfile(ENV_PATH):
Expand All @@ -33,6 +35,8 @@ def str_to_bool(s: str) -> bool:
# Workers
CVR = os.environ['cvr']
KOMBIT_CERT_PATH = os.environ['kombit_cert_path']
if not os.path.isfile(KOMBIT_CERT_PATH):
raise ValueError(f"Couldn't find certificate file: {KOMBIT_CERT_PATH}")
KOMBIT_TEST_ENV = str_to_bool(os.environ['Kombit_test_env'])

# Registration worker
Expand All @@ -41,7 +45,10 @@ def str_to_bool(s: str) -> bool:
# Shipment worker
SHIPMENT_WORKER_SLEEP_TIME = float(os.environ['shipment_worker_sleep_time'])
SENDER_LABEL = os.environ['sender_label']
PATH_TO_LIBREOFFICE = os.environ['path_to_libreoffice']

# Message broker worker
MESSAGE_BROKER_QUEUE_ID = os.environ['message_broker_queue_id']
MESSAGE_BROKER_WORKER_SLEEP_TIME = float(os.environ['message_broker_worker_sleep_time'])

# OIDC
CLIENT_ID = os.environ['client_id']
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ class JobType(Enum):
DIGITAL_POST = "digitalpost"


# We don't care about duplicate code for ORM classes.
# pylint: disable=duplicate-code
class RegistrationJob(Base):
"""An ORM class representing a registration job.
A job is a collection of multiple tasks.
Expand All @@ -40,6 +42,7 @@ def to_row_dict(self) -> dict[str, str]:
"created_at": self.created_at.strftime("%d-%m-%Y %H:%M:%S"),
"created_by": self.created_by
}
# pylint: enable=duplicate-code


def add_registation_job(name: str, description: str, job_type: JobType, created_by: str) -> int:
Expand Down
2 changes: 1 addition & 1 deletion src/OpenPostbud/database/digital_post/db_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ def calculate_shipment_status(shipment_id: str) -> list[tuple[str, int]]:
"""
with connection.get_session() as session:
query = (
select(Letter.status, func.count(Letter.status))
select(Letter.status, func.count(Letter.status)) # pylint: disable=not-callable
.where(Letter.shipment_id == shipment_id)
.group_by(Letter.status)
)
Expand Down
Loading