Compare commits
20
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
256cb98695 | ||
|
|
1cd6fc11a4 | ||
|
|
9643916f16 | ||
|
|
f52a4ef3cf | ||
|
|
ebdf3f4f91 | ||
|
|
ed8f59439d | ||
|
|
5c287f2459 | ||
|
|
fdb71081e0 | ||
|
|
751bf8be16 | ||
|
|
e738cba3a3 | ||
|
|
4c00ccaa39 | ||
|
|
36f41ba43a | ||
|
|
61502548d4 | ||
|
|
7b6f6d43fc | ||
|
|
4822ee6880 | ||
|
|
3cfb2260cc | ||
|
|
b5045cf487 | ||
|
|
a6c8a79e31 | ||
|
|
2419e6bf01 | ||
|
|
3ab9127548 |
@@ -1,62 +0,0 @@
|
|||||||
name: deploy
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
- master
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
|
|
||||||
concurrency:
|
|
||||||
group: deploy-${{ github.ref }}
|
|
||||||
cancel-in-progress: false
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
deploy:
|
|
||||||
runs-on: self-hosted
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- name: Push-to-deploy op server
|
|
||||||
run: |
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
if [ -z "${DEPLOY_COMPOSE_FILE:-}" ]; then
|
|
||||||
DEPLOY_COMPOSE_FILE="docker-compose.yml"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -z "${APP_HOST:-}" ]; then
|
|
||||||
APP_HOST="$(hostname -f 2>/dev/null || hostname)"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -z "${APP_SCHEME:-}" ]; then
|
|
||||||
APP_SCHEME="http"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -z "${DJANGO_DEBUG:-}" ]; then
|
|
||||||
if [ "${APP_SCHEME}" = "https" ]; then
|
|
||||||
DJANGO_DEBUG="0"
|
|
||||||
else
|
|
||||||
DJANGO_DEBUG="1"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
export APP_HOST
|
|
||||||
export APP_SCHEME
|
|
||||||
export DJANGO_DEBUG
|
|
||||||
|
|
||||||
export DEPLOY_COMPOSE_FILE
|
|
||||||
if [ -n "${APP_ORIGINS:-}" ]; then
|
|
||||||
export APP_ORIGINS
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -n "${APP_HOSTS:-}" ]; then
|
|
||||||
export APP_HOSTS
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -n "${APP_PORT:-}" ]; then
|
|
||||||
export APP_PORT
|
|
||||||
fi
|
|
||||||
|
|
||||||
bash scripts/deploy_docker.sh "${DEPLOY_COMPOSE_FILE}" .env
|
|
||||||
@@ -0,0 +1,133 @@
|
|||||||
|
name: Managed validation
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
profile:
|
||||||
|
description: Allowlisted validation profile
|
||||||
|
required: true
|
||||||
|
default: full
|
||||||
|
type: choice
|
||||||
|
options: [test, lint, typecheck, build, security, full]
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: managed-validation-${{ gitea.repository }}-${{ gitea.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
full:
|
||||||
|
name: full
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 30
|
||||||
|
container:
|
||||||
|
# Gitea's JavaScript actions execute inside the job container, so use
|
||||||
|
# the version-matched Playwright image that includes Node as well as the
|
||||||
|
# pinned Chromium browser and native runtime libraries.
|
||||||
|
image: mcr.microsoft.com/playwright:v1.54.0-noble
|
||||||
|
options: --ipc=host
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
|
- name: Provision the supported Python runtime
|
||||||
|
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065
|
||||||
|
with:
|
||||||
|
python-version: '3.13.2'
|
||||||
|
- name: Validate repository with a bounded profile
|
||||||
|
shell: bash
|
||||||
|
env:
|
||||||
|
REQUESTED_PROFILE: ${{ inputs.profile }}
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
profile="${REQUESTED_PROFILE:-full}"
|
||||||
|
case "${profile}" in
|
||||||
|
test|lint|typecheck|build|security|full) ;;
|
||||||
|
*) echo "Profile is not allowlisted" >&2; exit 2 ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
git diff --check
|
||||||
|
if git grep -nE '^(<<<<<<< |=======$|>>>>>>> )' -- . ':!*.lock' ':!*.patch'; then
|
||||||
|
echo "Unresolved merge markers detected" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -f pyproject.toml || -f requirements.txt ]]; then
|
||||||
|
# Compile only tracked Python sources. Running compileall after a
|
||||||
|
# Node install would otherwise traverse node_modules and turn a
|
||||||
|
# lightweight baseline into a large runner workload.
|
||||||
|
git ls-files -z '*.py' | xargs -0 -r python -m py_compile
|
||||||
|
if [[ -f uv.lock ]]; then
|
||||||
|
python -m venv "${RUNNER_TEMP}/managed-uv"
|
||||||
|
uv_python="${RUNNER_TEMP}/managed-uv/bin/python"
|
||||||
|
"${uv_python}" -m pip install --disable-pip-version-check uv==0.10.0
|
||||||
|
managed_uv="${RUNNER_TEMP}/managed-uv/bin/uv"
|
||||||
|
export UV_PROJECT_ENVIRONMENT="${RUNNER_TEMP}/managed-project-venv"
|
||||||
|
"${managed_uv}" sync --locked --extra browser
|
||||||
|
export PATH="${UV_PROJECT_ENVIRONMENT}/bin:${PATH}"
|
||||||
|
if [[ "${profile}" == test || "${profile}" == full ]]; then
|
||||||
|
if "${managed_uv}" run python -c 'import pytest' 2>/dev/null; then
|
||||||
|
# The image supplies the native browser libraries. Install
|
||||||
|
# only the lockfile-matched browser payload into an explicit
|
||||||
|
# job-local path; never invoke Playwright's apt workflow.
|
||||||
|
export PLAYWRIGHT_BROWSERS_PATH="${RUNNER_TEMP}/managed-playwright"
|
||||||
|
timeout --foreground 5m "${managed_uv}" run playwright install chromium
|
||||||
|
timeout --foreground 20m "${managed_uv}" run python -m pytest
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
if [[ "${profile}" == lint || "${profile}" == full ]]; then
|
||||||
|
if "${managed_uv}" run python -c 'import ruff' 2>/dev/null; then
|
||||||
|
"${managed_uv}" run python -m ruff check .
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
elif [[ -f requirements.txt ]]; then
|
||||||
|
python -m venv "${RUNNER_TEMP}/managed-python"
|
||||||
|
managed_python="${RUNNER_TEMP}/managed-python/bin/python"
|
||||||
|
"${managed_python}" -m pip install --disable-pip-version-check -r requirements.txt
|
||||||
|
export PATH="${RUNNER_TEMP}/managed-python/bin:${PATH}"
|
||||||
|
if [[ "${profile}" == test || "${profile}" == full ]]; then
|
||||||
|
if "${managed_python}" -c 'import pytest' 2>/dev/null; then
|
||||||
|
"${managed_python}" -m pytest
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Prepare Python before invoking Node scripts. Polyglot repositories
|
||||||
|
# commonly delegate their test script to Python and need the managed
|
||||||
|
# virtual environment to be active first.
|
||||||
|
if [[ -f package.json ]]; then
|
||||||
|
corepack enable
|
||||||
|
if [[ -f pnpm-lock.yaml ]]; then
|
||||||
|
pnpm install --frozen-lockfile
|
||||||
|
[[ "${profile}" == test || "${profile}" == full ]] && pnpm --if-present test
|
||||||
|
[[ "${profile}" == lint || "${profile}" == full ]] && pnpm --if-present lint
|
||||||
|
[[ "${profile}" == typecheck || "${profile}" == full ]] && pnpm --if-present typecheck
|
||||||
|
[[ "${profile}" == build || "${profile}" == full ]] && pnpm --if-present build
|
||||||
|
elif [[ -f package-lock.json ]]; then
|
||||||
|
npm ci
|
||||||
|
[[ "${profile}" == test || "${profile}" == full ]] && npm run --if-present test
|
||||||
|
[[ "${profile}" == lint || "${profile}" == full ]] && npm run --if-present lint
|
||||||
|
if [[ "${profile}" == typecheck || "${profile}" == full ]]; then
|
||||||
|
npm run --if-present typecheck
|
||||||
|
fi
|
||||||
|
[[ "${profile}" == build || "${profile}" == full ]] && npm run --if-present build
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -f go.mod ]]; then
|
||||||
|
if [[ "${profile}" == test || "${profile}" == build || "${profile}" == full ]]; then
|
||||||
|
go test ./...
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
if [[ -f Cargo.toml ]]; then
|
||||||
|
if [[ "${profile}" == test || "${profile}" == build || "${profile}" == full ]]; then
|
||||||
|
cargo test --locked
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
if compgen -G '*.sln' >/dev/null; then
|
||||||
|
if [[ "${profile}" == test || "${profile}" == build || "${profile}" == full ]]; then
|
||||||
|
dotnet test --configuration Release
|
||||||
|
fi
|
||||||
|
fi
|
||||||
@@ -10,8 +10,10 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: astral-sh/setup-uv@v6
|
- name: Install pinned uv
|
||||||
|
uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
|
||||||
with:
|
with:
|
||||||
|
version: "0.11.12"
|
||||||
python-version: "3.13"
|
python-version: "3.13"
|
||||||
- name: Install verification browser
|
- name: Install verification browser
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
name: Unraid autoredeploy
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [main]
|
||||||
|
paths-ignore:
|
||||||
|
- ".gitea/**"
|
||||||
|
- "docs/**"
|
||||||
|
- "**/*.md"
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: unraid-production-vacatureradar
|
||||||
|
cancel-in-progress: false
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
deploy:
|
||||||
|
name: Deploy vacatureradar
|
||||||
|
runs-on: unraid-deploy
|
||||||
|
timeout-minutes: 180
|
||||||
|
steps:
|
||||||
|
- name: Deploy exact Gitea revision
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
docker exec gitea-deploy-control \
|
||||||
|
/opt/gitea-deploy/deploy.py deploy \
|
||||||
|
"$GITHUB_REPOSITORY" "$GITHUB_SHA"
|
||||||
|
|
||||||
@@ -32,7 +32,7 @@ jobs:
|
|||||||
path: ./
|
path: ./
|
||||||
extra_args: --only-verified
|
extra_args: --only-verified
|
||||||
- name: Scan project dependencies and lockfiles (high/critical)
|
- name: Scan project dependencies and lockfiles (high/critical)
|
||||||
uses: aquasecurity/trivy-action@0.30.0
|
uses: aquasecurity/trivy-action@v0.30.0
|
||||||
with:
|
with:
|
||||||
scan-type: fs
|
scan-type: fs
|
||||||
scan-ref: .
|
scan-ref: .
|
||||||
@@ -66,7 +66,7 @@ jobs:
|
|||||||
- name: Build production image
|
- name: Build production image
|
||||||
run: docker build --pull --tag vacatureradar:ci .
|
run: docker build --pull --tag vacatureradar:ci .
|
||||||
- name: Scan container image for high/critical vulnerabilities
|
- name: Scan container image for high/critical vulnerabilities
|
||||||
uses: aquasecurity/trivy-action@0.30.0
|
uses: aquasecurity/trivy-action@v0.30.0
|
||||||
with:
|
with:
|
||||||
image-ref: vacatureradar:ci
|
image-ref: vacatureradar:ci
|
||||||
format: table
|
format: table
|
||||||
@@ -74,7 +74,7 @@ jobs:
|
|||||||
exit-code: "1"
|
exit-code: "1"
|
||||||
ignore-unfixed: true
|
ignore-unfixed: true
|
||||||
- name: Generate CycloneDX software bill of materials
|
- name: Generate CycloneDX software bill of materials
|
||||||
uses: aquasecurity/trivy-action@0.30.0
|
uses: aquasecurity/trivy-action@v0.30.0
|
||||||
with:
|
with:
|
||||||
image-ref: vacatureradar:ci
|
image-ref: vacatureradar:ci
|
||||||
format: cyclonedx
|
format: cyclonedx
|
||||||
|
|||||||
@@ -2,6 +2,27 @@
|
|||||||
|
|
||||||
Alle betekenisvolle wijzigingen worden hier bijgehouden. Het project volgt voorlopig een pre-1.0 semantische versieaanpak.
|
Alle betekenisvolle wijzigingen worden hier bijgehouden. Het project volgt voorlopig een pre-1.0 semantische versieaanpak.
|
||||||
|
|
||||||
|
## 0.3.17 - 2026-08-12
|
||||||
|
|
||||||
|
### Hersteld
|
||||||
|
|
||||||
|
- De Gitea-releasebootstrap gebruikt een immutable `setup-uv`-commit en een exacte
|
||||||
|
uv-versie; downloads lopen via de Astral-mirror en zijn niet langer afhankelijk
|
||||||
|
van de gelimiteerde GitHub Releases-API op het gedeelde runner-IP.
|
||||||
|
- De achterhaalde `main`-push-to-deployworkflow is verwijderd; alleen een
|
||||||
|
semantische tag kan nog een immutable releaseimage publiceren.
|
||||||
|
- De Unraid Docker-host gebruikt een conflictvrije `/24`-address pool met ruimte
|
||||||
|
voor 256 tijdelijke en toekomstige netwerken.
|
||||||
|
|
||||||
|
## 0.3.16 - 2026-08-12
|
||||||
|
|
||||||
|
### Hersteld
|
||||||
|
|
||||||
|
- De publieke demo herstelt bij iedere nieuwe demosessie zes actuele fictieve vacatures, hun laatste scores en twee toekomstige opvolgmomenten zonder duplicaten of onbeperkte scorehistoriek.
|
||||||
|
- Negen fictieve `example.test`-platformmailboxen maken de mailboxflow zichtbaar zonder echte accounts, credentials of externe polls.
|
||||||
|
- Mailboxingress wordt uitsluitend door de mailboxscheduler verwerkt; e-mailbronnen komen niet meer in URL-fetchplanning, dashboardscanstatus of de algemene bronrunactiviteit terecht.
|
||||||
|
- Statuscopy onderscheidt een actief zoekprofiel van operationele brongezondheid en het lege loginwachtwoordveld lijkt niet langer vooraf ingevuld.
|
||||||
|
|
||||||
## 0.3.14 - 2026-07-29
|
## 0.3.14 - 2026-07-29
|
||||||
|
|
||||||
### Gewijzigd
|
### Gewijzigd
|
||||||
|
|||||||
@@ -8,6 +8,13 @@
|
|||||||
|
|
||||||
VacatureRadar is een persoonlijke vacature-assistent voor België. De applicatie volgt toegestane werkgeversbronnen, ATS-pagina’s, feeds en vacaturemails, verwijdert dubbels en irrelevante resultaten en rangschikt de overblijvende kansen tegen jouw zoekprofiel. Jij behoudt altijd de controle: VacatureRadar helpt zoeken en opvolgen, maar solliciteert nooit automatisch.
|
VacatureRadar is een persoonlijke vacature-assistent voor België. De applicatie volgt toegestane werkgeversbronnen, ATS-pagina’s, feeds en vacaturemails, verwijdert dubbels en irrelevante resultaten en rangschikt de overblijvende kansen tegen jouw zoekprofiel. Jij behoudt altijd de controle: VacatureRadar helpt zoeken en opvolgen, maar solliciteert nooit automatisch.
|
||||||
|
|
||||||
|
**Actuele release: 0.3.17.** De volledige P0/P1-releasebasis en de publieke
|
||||||
|
demo-hardening zijn afgerond: 328 tests zijn groen, Microsoft 365 verwerkt platformmails via
|
||||||
|
app-only OAuth/XOAUTH2. Release 0.3.17 herstelt bovendien de reproduceerbare
|
||||||
|
private-registrypublicatie zonder afhankelijkheid van de GitHub Releases-API.
|
||||||
|
Het controleerbare acceptatiebewijs staat in
|
||||||
|
[Finale acceptance](docs/audit/FINAL_ACCEPTANCE.md).
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
## Voor wie is dit?
|
## Voor wie is dit?
|
||||||
@@ -37,12 +44,15 @@ VacatureRadar is bedoeld voor één persoon of een eigen, afgeschermde installat
|
|||||||
| Vacatureverkenner | Zoeken en filteren met een uitlegbare detailanalyse naast de resultaten |
|
| Vacatureverkenner | Zoeken en filteren met een uitlegbare detailanalyse naast de resultaten |
|
||||||
| Skillsradar | Actuele marktvraag naast de skills uit je eigen profiel |
|
| Skillsradar | Actuele marktvraag naast de skills uit je eigen profiel |
|
||||||
| Sollicitatiepipeline | Dossiers, status, notities, deadlines en een auditbare tijdlijn |
|
| Sollicitatiepipeline | Dossiers, status, notities, deadlines en een auditbare tijdlijn |
|
||||||
| Bronnenbeheer | Veilige bronconfiguratie, gezondheid, handmatige import en mailboxkoppelingen |
|
| Bronnenbeheer | Veilige bronconfiguratie, gezondheid, handmatige import en afzonderlijke mailboxkoppelingen voor negen jobplatformen |
|
||||||
| Automatisering | Eerlijke 24-uursmetingen; niet-gemeten informatie blijft expliciet onbekend |
|
| Automatisering | Eerlijke 24-uursmetingen; niet-gemeten informatie blijft expliciet onbekend |
|
||||||
|
|
||||||
## Bekijk de volledige productflow
|
## Bekijk de volledige productflow
|
||||||
|
|
||||||
De beelden komen uit de lokale demo-omgeving. De getoonde cijfers zijn omgevingsdata, geen marketingclaims. Klik op een beeld voor de volledige resolutie.
|
De beelden komen uit een gesaneerde lokale demo-omgeving. De getoonde aantallen
|
||||||
|
zijn een momentopname van die demo en bewijzen geen productie-status; daarvoor
|
||||||
|
geldt uitsluitend de gedateerde [finale acceptance](docs/audit/FINAL_ACCEPTANCE.md).
|
||||||
|
Klik op een beeld voor de volledige resolutie.
|
||||||
|
|
||||||
### 1. Vind de relevante kansen
|
### 1. Vind de relevante kansen
|
||||||
|
|
||||||
@@ -78,7 +88,7 @@ Een analyse maakt score, datakwaliteit, beslissignalen, aandachtspunten en broni
|
|||||||
<table>
|
<table>
|
||||||
<tr>
|
<tr>
|
||||||
<td width="50%"><strong>Sollicitatiepipeline</strong><br><sub>Bewaar status, deadlines, notities en tijdlijn per dossier; de uiteindelijke sollicitatie blijft altijd handmatig.</sub></td>
|
<td width="50%"><strong>Sollicitatiepipeline</strong><br><sub>Bewaar status, deadlines, notities en tijdlijn per dossier; de uiteindelijke sollicitatie blijft altijd handmatig.</sub></td>
|
||||||
<td width="50%"><strong>Bronnenbeheer</strong><br><sub>Zie policyreview, laatste scans, volgende runs en brongezondheid zonder technische logs te moeten doorzoeken.</sub></td>
|
<td width="50%"><strong>Bronnenbeheer</strong><br><sub>Zie policyreview, laatste scans, volgende runs, brongezondheid en de status van iedere platformmailbox zonder technische logs te moeten doorzoeken.</sub></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><a href="docs/screenshots/readme/applications-dark.png"><img src="docs/screenshots/readme/applications-dark.png" alt="Sollicitatiepipeline met persoonlijke dossiers"></a></td>
|
<td><a href="docs/screenshots/readme/applications-dark.png"><img src="docs/screenshots/readme/applications-dark.png" alt="Sollicitatiepipeline met persoonlijke dossiers"></a></td>
|
||||||
@@ -141,7 +151,7 @@ Na de eerste login:
|
|||||||
|
|
||||||
1. Open **Zoekprofiel** en stel regio, radius, rollen, skills en werkvoorkeuren in.
|
1. Open **Zoekprofiel** en stel regio, radius, rollen, skills en werkvoorkeuren in.
|
||||||
2. Open **Bronnen** en activeer alleen bronnen waarvan beleid en voorwaarden beoordeeld zijn.
|
2. Open **Bronnen** en activeer alleen bronnen waarvan beleid en voorwaarden beoordeeld zijn.
|
||||||
3. Configureer optioneel een eigen vacaturemailbox; credentials worden niet in de repository opgeslagen.
|
3. Koppel optioneel afzonderlijke vacaturemailboxen voor VDAB, Indeed, LinkedIn, ictjob.be, Jobat, StepStone, Careerjet, Randstad en Robert Half. Credentials worden niet in de repository opgeslagen.
|
||||||
4. Controleer **Automatisering** voordat je op onbemande verwerking vertrouwt.
|
4. Controleer **Automatisering** voordat je op onbemande verwerking vertrouwt.
|
||||||
5. Verifieer in productie altijd HTTPS, back-ups, herstel en healthchecks.
|
5. Verifieer in productie altijd HTTPS, back-ups, herstel en healthchecks.
|
||||||
|
|
||||||
@@ -158,6 +168,12 @@ De publieke URL wordt vervolgens gebruikt voor `ALLOWED_HOSTS`, CSRF, secure coo
|
|||||||
|
|
||||||
Publiceer poort `1226` niet rechtstreeks op internet. Gebruik een beheerde reverse proxy met TLS en volg [Unraid-deployment](docs/operations/UNRAID_DEPLOYMENT.md) en de concrete [Nginx Proxy Manager-handleiding](docs/operations/NGINX_PROXY_MANAGER.md). Een volledig productievoorbeeld staat in [`deployment/production.env.example`](deployment/production.env.example).
|
Publiceer poort `1226` niet rechtstreeks op internet. Gebruik een beheerde reverse proxy met TLS en volg [Unraid-deployment](docs/operations/UNRAID_DEPLOYMENT.md) en de concrete [Nginx Proxy Manager-handleiding](docs/operations/NGINX_PROXY_MANAGER.md). Een volledig productievoorbeeld staat in [`deployment/production.env.example`](deployment/production.env.example).
|
||||||
|
|
||||||
|
Voor Microsoft 365 gebruikt VacatureRadar uitsluitend app-only OAuth/XOAUTH2
|
||||||
|
voor IMAP en SMTP; Basic Auth en Microsoft 365-app-wachtwoorden worden niet
|
||||||
|
ondersteund. Beperk de Entra-serviceprincipal tot de bedoelde mailbox en geef
|
||||||
|
alleen `IMAP.AccessAsApp` en `SMTP.SendAsApp`. De exacte configuratie- en
|
||||||
|
rotatiestappen staan in het [operationele runbook](docs/operations/RUNBOOK.md).
|
||||||
|
|
||||||
## Eerlijke grenzen
|
## Eerlijke grenzen
|
||||||
|
|
||||||
VacatureRadar:
|
VacatureRadar:
|
||||||
@@ -193,6 +209,11 @@ Die controleert formatting, lint, Django-configuratie, migraties, de volledige t
|
|||||||
|
|
||||||
## Beheer en deployment
|
## Beheer en deployment
|
||||||
|
|
||||||
|
Productiereleases worden door Gitea Actions gebouwd, gescand en als private
|
||||||
|
containerimage gepubliceerd. Pin Unraid op `versie@sha256:digest`, bewaar de
|
||||||
|
vorige digest voor rollback en gebruik nooit alleen een veranderlijke tag als
|
||||||
|
productiereferentie.
|
||||||
|
|
||||||
- [Lokaal ontwikkelen](docs/operations/LOCAL_DEVELOPMENT.md)
|
- [Lokaal ontwikkelen](docs/operations/LOCAL_DEVELOPMENT.md)
|
||||||
- [Docker en Unraid uitrollen](docs/operations/UNRAID_DEPLOYMENT.md)
|
- [Docker en Unraid uitrollen](docs/operations/UNRAID_DEPLOYMENT.md)
|
||||||
- [Nginx Proxy Manager configureren](docs/operations/NGINX_PROXY_MANAGER.md)
|
- [Nginx Proxy Manager configureren](docs/operations/NGINX_PROXY_MANAGER.md)
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ from django.urls import reverse
|
|||||||
|
|
||||||
from apps.notifications.models import DigestOutbox, ReminderOutbox
|
from apps.notifications.models import DigestOutbox, ReminderOutbox
|
||||||
from apps.profiles.models import ProfileRevision
|
from apps.profiles.models import ProfileRevision
|
||||||
from apps.sources.models import SourceRun
|
from apps.sources.models import Source, SourceRun
|
||||||
|
|
||||||
from ..models import ApplicationTimelineEvent, Feedback, JobVersion
|
from ..models import ApplicationTimelineEvent, Feedback, JobVersion
|
||||||
|
|
||||||
@@ -37,7 +37,8 @@ ACTIVITY_FILTERS = (
|
|||||||
|
|
||||||
def _source_events() -> list[ActivityEvent]:
|
def _source_events() -> list[ActivityEvent]:
|
||||||
events = []
|
events = []
|
||||||
for run in SourceRun.objects.select_related("source").order_by("-started_at")[:100]:
|
runs = SourceRun.objects.exclude(source__source_type=Source.Type.EMAIL)
|
||||||
|
for run in runs.select_related("source").order_by("-started_at")[:100]:
|
||||||
detail = (
|
detail = (
|
||||||
f"{run.extracted_count} geëxtraheerd · {run.created_count} nieuw · "
|
f"{run.extracted_count} geëxtraheerd · {run.created_count} nieuw · "
|
||||||
f"{run.duplicate_count} duplicaat"
|
f"{run.duplicate_count} duplicaat"
|
||||||
|
|||||||
@@ -57,7 +57,8 @@ def annotate_latest_profile_score(
|
|||||||
|
|
||||||
def _recent_dashboard_activity(user: User) -> list[dict[str, Any]]:
|
def _recent_dashboard_activity(user: User) -> list[dict[str, Any]]:
|
||||||
activity: list[dict[str, Any]] = []
|
activity: list[dict[str, Any]] = []
|
||||||
for run in SourceRun.objects.select_related("source").order_by("-started_at")[:4]:
|
source_runs = SourceRun.objects.exclude(source__source_type=Source.Type.EMAIL)
|
||||||
|
for run in source_runs.select_related("source").order_by("-started_at")[:4]:
|
||||||
activity.append(
|
activity.append(
|
||||||
{
|
{
|
||||||
"occurred_at": run.started_at,
|
"occurred_at": run.started_at,
|
||||||
@@ -154,7 +155,9 @@ def build_dashboard_cockpit(user: User, profile: SearchProfile | None) -> dict[s
|
|||||||
"source_counts": source_counts,
|
"source_counts": source_counts,
|
||||||
"recent_activity": _recent_dashboard_activity(user),
|
"recent_activity": _recent_dashboard_activity(user),
|
||||||
"day_conclusion": day_conclusion,
|
"day_conclusion": day_conclusion,
|
||||||
"latest_scan": SourceRun.objects.order_by("-started_at").first(),
|
"latest_scan": SourceRun.objects.exclude(source__source_type=Source.Type.EMAIL)
|
||||||
|
.order_by("-started_at")
|
||||||
|
.first(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -122,11 +122,10 @@ def demo_environment_seeded(user) -> bool:
|
|||||||
|
|
||||||
|
|
||||||
def seed_demo_environment(user) -> None:
|
def seed_demo_environment(user) -> None:
|
||||||
"""Maak (idempotent) een fictieve demo-omgeving voor ``user``."""
|
"""Maak of herstel een fictieve, tijdsbestendige demo-omgeving voor ``user``."""
|
||||||
if demo_environment_seeded(user):
|
|
||||||
return
|
|
||||||
|
|
||||||
from apps.jobs.models import Application, Employer, JobPosting, ScoreRun
|
from apps.jobs.models import Application, Employer, JobPosting, ScoreRun
|
||||||
|
from apps.sources.models import MailboxConnection, Source, SourceRun
|
||||||
|
from apps.sources.platforms import PLATFORM_ALERTS
|
||||||
|
|
||||||
profile, _ = SearchProfile.objects.get_or_create(
|
profile, _ = SearchProfile.objects.get_or_create(
|
||||||
user=user,
|
user=user,
|
||||||
@@ -185,7 +184,7 @@ def seed_demo_environment(user) -> None:
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
slug = _digest("demo-job", str(index), title, employer_name)
|
slug = _digest("demo-job", str(index), title, employer_name)
|
||||||
job, job_created = JobPosting.objects.get_or_create(
|
job, _ = JobPosting.objects.update_or_create(
|
||||||
canonical_key=slug,
|
canonical_key=slug,
|
||||||
defaults={
|
defaults={
|
||||||
"employer": employer,
|
"employer": employer,
|
||||||
@@ -221,6 +220,11 @@ def seed_demo_environment(user) -> None:
|
|||||||
)
|
)
|
||||||
created_jobs.append(job)
|
created_jobs.append(job)
|
||||||
|
|
||||||
|
ScoreRun.objects.filter(
|
||||||
|
job=job,
|
||||||
|
profile=profile,
|
||||||
|
model_version="demo-seed",
|
||||||
|
).delete()
|
||||||
ScoreRun.objects.create(
|
ScoreRun.objects.create(
|
||||||
job=job,
|
job=job,
|
||||||
profile=profile,
|
profile=profile,
|
||||||
@@ -244,7 +248,7 @@ def seed_demo_environment(user) -> None:
|
|||||||
|
|
||||||
# Een kleine sollicitatiepijplijn voor de demo.
|
# Een kleine sollicitatiepijplijn voor de demo.
|
||||||
if created_jobs:
|
if created_jobs:
|
||||||
Application.objects.get_or_create(
|
Application.objects.update_or_create(
|
||||||
user=user,
|
user=user,
|
||||||
job=created_jobs[0],
|
job=created_jobs[0],
|
||||||
defaults={
|
defaults={
|
||||||
@@ -257,7 +261,7 @@ def seed_demo_environment(user) -> None:
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
if len(created_jobs) > 3:
|
if len(created_jobs) > 3:
|
||||||
Application.objects.get_or_create(
|
Application.objects.update_or_create(
|
||||||
user=user,
|
user=user,
|
||||||
job=created_jobs[3],
|
job=created_jobs[3],
|
||||||
defaults={
|
defaults={
|
||||||
@@ -267,3 +271,42 @@ def seed_demo_environment(user) -> None:
|
|||||||
"notes": "Sollicitatie verstuurd. Demo-data.",
|
"notes": "Sollicitatie verstuurd. Demo-data.",
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Platformmailboxen zijn zichtbare ingressconfiguratie, geen crawlbare URL-bronnen.
|
||||||
|
# De demo gebruikt uitsluitend fictieve adressen en wordt door de scheduler overgeslagen.
|
||||||
|
for platform in PLATFORM_ALERTS:
|
||||||
|
MailboxConnection.objects.update_or_create(
|
||||||
|
user=user,
|
||||||
|
platform=platform,
|
||||||
|
defaults={
|
||||||
|
"provider": MailboxConnection.Provider.OUTLOOK,
|
||||||
|
"username": "alerts@example.test",
|
||||||
|
"mailbox": f"VacatureRadar/{platform}",
|
||||||
|
"enabled": True,
|
||||||
|
"poll_interval_minutes": MailboxConnection.PollInterval.THIRTY_MINUTES,
|
||||||
|
"next_poll_at": now + timedelta(minutes=30),
|
||||||
|
"last_polled_at": now - timedelta(minutes=5),
|
||||||
|
"last_success_at": now - timedelta(minutes=5),
|
||||||
|
"last_error_category": "",
|
||||||
|
"last_error_message": "",
|
||||||
|
"lease_token": "",
|
||||||
|
"lease_expires_at": None,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
source, _ = Source.objects.update_or_create(
|
||||||
|
source_type=Source.Type.EMAIL,
|
||||||
|
domain=f"{platform}.mailbox.local",
|
||||||
|
defaults={
|
||||||
|
"name": f"{PLATFORM_ALERTS[platform].label} vacaturemailbox",
|
||||||
|
"base_url": "",
|
||||||
|
"status": Source.Status.ACTIVE,
|
||||||
|
"policy": Source.Policy.ALLOW,
|
||||||
|
"policy_reason": "Mailboxingress; niet via URL-fetch gepland.",
|
||||||
|
"parser_key": "email-alert",
|
||||||
|
"failure_count": 0,
|
||||||
|
"last_failure_at": None,
|
||||||
|
"next_run_at": None,
|
||||||
|
"metadata": {"demo": True, "mailbox_ingress": True},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
SourceRun.objects.filter(source=source).delete()
|
||||||
|
|||||||
@@ -188,7 +188,8 @@ def schedule_due_sources() -> dict[str, int]:
|
|||||||
now = timezone.now()
|
now = timezone.now()
|
||||||
due = Source.objects.filter(
|
due = Source.objects.filter(
|
||||||
status__in=[Source.Status.ACTIVE, Source.Status.TRIAL],
|
status__in=[Source.Status.ACTIVE, Source.Status.TRIAL],
|
||||||
).filter(Q(next_run_at__isnull=True) | Q(next_run_at__lte=now))
|
).exclude(source_type=Source.Type.EMAIL)
|
||||||
|
due = due.filter(Q(next_run_at__isnull=True) | Q(next_run_at__lte=now))
|
||||||
ids = list(due.values_list("id", flat=True).distinct()[:200])
|
ids = list(due.values_list("id", flat=True).distinct()[:200])
|
||||||
for source_id in ids:
|
for source_id in ids:
|
||||||
fetch_source.delay(source_id)
|
fetch_source.delay(source_id)
|
||||||
@@ -200,6 +201,7 @@ def schedule_mailbox_polls() -> dict[str, int]:
|
|||||||
now = timezone.now()
|
now = timezone.now()
|
||||||
due_ids = list(
|
due_ids = list(
|
||||||
MailboxConnection.objects.filter(enabled=True, next_poll_at__lte=now)
|
MailboxConnection.objects.filter(enabled=True, next_poll_at__lte=now)
|
||||||
|
.exclude(user__username=settings.DEMO_USERNAME)
|
||||||
.filter(Q(lease_expires_at__isnull=True) | Q(lease_expires_at__lte=now))
|
.filter(Q(lease_expires_at__isnull=True) | Q(lease_expires_at__lte=now))
|
||||||
.values_list("id", flat=True)[:100]
|
.values_list("id", flat=True)[:100]
|
||||||
)
|
)
|
||||||
|
|||||||
+1
-1
@@ -139,7 +139,7 @@ CSRF_TRUSTED_ORIGINS = merge_unique(
|
|||||||
)
|
)
|
||||||
TIME_ZONE = os.getenv("DJANGO_TIME_ZONE", "Europe/Brussels")
|
TIME_ZONE = os.getenv("DJANGO_TIME_ZONE", "Europe/Brussels")
|
||||||
VACATURERADAR_OWNER_NAME = os.getenv("VACATURERADAR_OWNER_NAME", "Jens").strip()[:40] or "Jens"
|
VACATURERADAR_OWNER_NAME = os.getenv("VACATURERADAR_OWNER_NAME", "Jens").strip()[:40] or "Jens"
|
||||||
VACATURERADAR_VERSION = os.getenv("VACATURERADAR_VERSION", "0.3.14").strip() or "0.3.14"
|
VACATURERADAR_VERSION = os.getenv("VACATURERADAR_VERSION", "0.3.17").strip() or "0.3.17"
|
||||||
LANGUAGE_CODE = "nl-be"
|
LANGUAGE_CODE = "nl-be"
|
||||||
USE_I18N = True
|
USE_I18N = True
|
||||||
USE_TZ = True
|
USE_TZ = True
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ DJANGO_ALLOWED_HOSTS=vacatureradar.example.be,127.0.0.1,localhost
|
|||||||
DJANGO_CSRF_TRUSTED_ORIGINS=https://vacatureradar.example.be
|
DJANGO_CSRF_TRUSTED_ORIGINS=https://vacatureradar.example.be
|
||||||
DJANGO_TIME_ZONE=Europe/Brussels
|
DJANGO_TIME_ZONE=Europe/Brussels
|
||||||
VACATURERADAR_OWNER_NAME=Jens
|
VACATURERADAR_OWNER_NAME=Jens
|
||||||
VACATURERADAR_VERSION=0.3.14
|
VACATURERADAR_VERSION=0.3.17
|
||||||
|
|
||||||
POSTGRES_DB=vacatureradar
|
POSTGRES_DB=vacatureradar
|
||||||
POSTGRES_USER=vacatureradar
|
POSTGRES_USER=vacatureradar
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
# Repository sanitation status
|
||||||
|
|
||||||
|
The accidental deployment log containing live root-SSH/container output was removed from the current tree. Active Unraid examples now use a neutral local hostname and generic appdata/backup paths rather than the operator's private host and storage layout.
|
||||||
|
|
||||||
|
## HISTORY_REWRITE_REQUIRED
|
||||||
|
|
||||||
|
Earlier commits contain the removed `deploy-log.txt` object, including private deployment commands, host information and runtime/database output. Earlier `.env.unraid.example`, `BACKUP_RESTORE.md` and `DEPLOYMENT.md` revisions also contain the original private host and appdata/backup paths.
|
||||||
|
|
||||||
|
Before public review, scan all refs for credentials, SSH targets, personal applicant/job data, production databases, media, backups, logs, archives and large objects. Review fixtures and screenshots for names, contact details, application history and other personal data. No history was rewritten during this campaign.
|
||||||
+116
-11
@@ -1,6 +1,6 @@
|
|||||||
schema_version: 1
|
schema_version: 1
|
||||||
project: VacatureRadar
|
project: VacatureRadar
|
||||||
updated_at: '2026-07-29'
|
updated_at: '2026-08-12'
|
||||||
execution_policy:
|
execution_policy:
|
||||||
order: file-order, then first ready task whose dependencies are done
|
order: file-order, then first ready task whose dependencies are done
|
||||||
no_product_questions: true
|
no_product_questions: true
|
||||||
@@ -1341,7 +1341,7 @@ tasks:
|
|||||||
gebruikt strikt de gepinde certifi-bundel zonder bypass.
|
gebruikt strikt de gepinde certifi-bundel zonder bypass.
|
||||||
- id: VR-202
|
- id: VR-202
|
||||||
title: Live IMAP- en SMTP-credentials configureren
|
title: Live IMAP- en SMTP-credentials configureren
|
||||||
status: blocked-external
|
status: done
|
||||||
priority: P0
|
priority: P0
|
||||||
requirement_ids:
|
requirement_ids:
|
||||||
- PR-016
|
- PR-016
|
||||||
@@ -1361,17 +1361,19 @@ tasks:
|
|||||||
- Dezelfde live testmail importeert per mailbox idempotent en alle geconfigureerde intervallen worden door de
|
- Dezelfde live testmail importeert per mailbox idempotent en alle geconfigureerde intervallen worden door de
|
||||||
scheduler gerespecteerd.
|
scheduler gerespecteerd.
|
||||||
- SMTP verstuurt één testdigest en outboxstatus klopt.
|
- SMTP verstuurt één testdigest en outboxstatus klopt.
|
||||||
- OAuth-clientcredentials en de encryptiesleutel staan alleen in de productie-secretstore/.env; eventuele
|
- OAuth-clientcredentials en de encryptiesleutel staan alleen in de productie-secretstore/.env; eventuele Gmail/custom-app-wachtwoorden
|
||||||
Gmail/custom-app-wachtwoorden zijn als ciphertext opgeslagen en sleutelrotatie is live getest.
|
zijn als ciphertext opgeslagen en sleutelrotatie is live getest.
|
||||||
verification:
|
verification:
|
||||||
- Volg Unraid mailboxchecklist
|
- Volg Unraid mailboxchecklist
|
||||||
- Inspecteer logs op afwezigheid van secret/payload
|
- Inspecteer logs op afwezigheid van secret/payload
|
||||||
primary_paths:
|
primary_paths:
|
||||||
- /mnt/user/appdata/vacatureradar/config/.env
|
- /mnt/user/appdata/VacatureRadar/.env
|
||||||
- Bronnen -> Platformmailboxen
|
- Bronnen -> Platformmailboxen
|
||||||
external_blocker: De dedicated productie-alias, Entra-appregistratie, admin consent en Exchange-mailboxscope zijn
|
result:
|
||||||
op 2026-07-30 live ingericht; resterend zijn de productie-uitrol en echte platformalertmails voor de
|
completed_at: '2026-07-30'
|
||||||
import-/digestproef.
|
note: M365 app-only OAuth/XOAUTH2 live ingericht met dedicated alias/map en Exchange-regel; negen platformmailboxen
|
||||||
|
importeerden dezelfde live testmail idempotent, schedulerstatus was correct, testdigest stond sent en logs
|
||||||
|
bleven payload-/secretvrij.
|
||||||
- id: VR-203
|
- id: VR-203
|
||||||
title: Productiedomein, TLS en reverse proxy activeren
|
title: Productiedomein, TLS en reverse proxy activeren
|
||||||
status: done
|
status: done
|
||||||
@@ -1403,7 +1405,7 @@ tasks:
|
|||||||
containerintern bereikbaar.
|
containerintern bereikbaar.
|
||||||
- id: VR-204
|
- id: VR-204
|
||||||
title: Immutable containerimage publiceren
|
title: Immutable containerimage publiceren
|
||||||
status: blocked-external
|
status: done
|
||||||
priority: P1
|
priority: P1
|
||||||
requirement_ids:
|
requirement_ids:
|
||||||
- NFR-009
|
- NFR-009
|
||||||
@@ -1421,9 +1423,12 @@ tasks:
|
|||||||
- Pull image op schone host
|
- Pull image op schone host
|
||||||
- Start smoke stack en rollback naar vorige digest
|
- Start smoke stack en rollback naar vorige digest
|
||||||
primary_paths:
|
primary_paths:
|
||||||
- .github/workflows/release.yml
|
- .gitea/workflows/release.yml
|
||||||
- docker-compose.unraid.yml
|
- docker-compose.unraid.yml
|
||||||
external_blocker: Vereist registrykeuze, account en credentials.
|
result:
|
||||||
|
completed_at: '2026-07-30'
|
||||||
|
note: Gitea private registry publiceerde v0.3.15 als immutable digest met commitlabel, SPDX-SBOM, checksums
|
||||||
|
en release notes; Unraid pull, digest-pin, healthy smoke en rollback naar 0.3.14 plus herstel zijn live geslaagd.
|
||||||
- id: VR-205
|
- id: VR-205
|
||||||
title: Gelicenseerde Belgische geodatabron laden
|
title: Gelicenseerde Belgische geodatabron laden
|
||||||
status: done
|
status: done
|
||||||
@@ -2371,3 +2376,103 @@ tasks:
|
|||||||
note: 'Release 0.3.14: geïsoleerde PostgreSQL-benchmark reduceert importduur 44,74% en queries minstens 63,73%;
|
note: 'Release 0.3.14: geïsoleerde PostgreSQL-benchmark reduceert importduur 44,74% en queries minstens 63,73%;
|
||||||
twee volledige gates met 318 tests groen; productie healthy op image sha256:1ee84241, transactionele importsmoke
|
twee volledige gates met 318 tests groen; productie healthy op image sha256:1ee84241, transactionele importsmoke
|
||||||
teruggerold en rollbackimage 0.3.13 beschikbaar.'
|
teruggerold en rollbackimage 0.3.13 beschikbaar.'
|
||||||
|
- id: VR-229
|
||||||
|
title: Herstel de publieke demo na de visuele 0.3.15-audit
|
||||||
|
status: done
|
||||||
|
priority: P1
|
||||||
|
requirement_ids:
|
||||||
|
- NFR-008
|
||||||
|
- NFR-009
|
||||||
|
depends_on:
|
||||||
|
- VR-228
|
||||||
|
summary: Maak de publieke demo opnieuw representatief en tijdsbestendig, scheid mailboxingress van URL-fetching
|
||||||
|
en laat operationele statussen exact zeggen wat ze meten.
|
||||||
|
acceptance_criteria:
|
||||||
|
- Iedere nieuwe demosessie herstelt zes fictieve actuele vacatures, scores en toekomstige opvolgdatums zonder
|
||||||
|
duplicaten of groeiende ScoreRun-historiek.
|
||||||
|
- De demo toont negen fictieve, actieve platformmailboxkoppelingen zonder secrets of echte persoonsgegevens.
|
||||||
|
- E-mailbronnen worden nooit door de generieke URL-fetchscheduler gepland en oude mailboxpolicyfouten vervuilen
|
||||||
|
dashboard en activiteit niet meer.
|
||||||
|
- Navigatie en dashboard noemen het zoekprofiel expliciet actief zonder brongezondheid te suggereren; het lege
|
||||||
|
loginwachtwoordveld lijkt niet vooraf ingevuld.
|
||||||
|
- De negen kernroutes blijven in dark/light en op mobiel, desktop en ultrawide zonder overflow, consolefouten
|
||||||
|
of headingregressies bruikbaar; de volledige kwaliteitsgate slaagt.
|
||||||
|
verification:
|
||||||
|
- python -m pytest tests/integration/test_auth_login.py tests/integration/test_tasks_and_more_views.py
|
||||||
|
- Browseraudit op 390x844, 1280x600, 1440x900 en 3440x1440
|
||||||
|
- ./scripts/codex_verify.sh
|
||||||
|
primary_paths:
|
||||||
|
- apps/jobs/services/demo_seed.py
|
||||||
|
- apps/jobs/services/activity.py
|
||||||
|
- apps/jobs/services/cockpit.py
|
||||||
|
- apps/sources/tasks.py
|
||||||
|
- templates/base.html
|
||||||
|
- templates/dashboard/today.html
|
||||||
|
- templates/registration/login.html
|
||||||
|
- tests/integration/test_auth_login.py
|
||||||
|
- tests/integration/test_tasks_and_more_views.py
|
||||||
|
result:
|
||||||
|
completed_at: '2026-08-12'
|
||||||
|
note: Publieke demo wordt bij iedere sessie idempotent hersteld met 6 actuele fictieve vacatures, tijdsbestendige
|
||||||
|
opvolging en 9 fictieve mailboxkoppelingen; EMAIL-bronnen en demo-mailboxen zijn uit de verkeerde schedulers
|
||||||
|
en activityfeeds uitgesloten, statuscopy en loginplaceholder zijn gecorrigeerd. Gerichte 51 tests en volledige
|
||||||
|
gate met 327 tests groen, 84,04% dekking.
|
||||||
|
- id: VR-230
|
||||||
|
title: Herstel de immutable Gitea-releasepublicatie
|
||||||
|
status: done
|
||||||
|
priority: P0
|
||||||
|
requirement_ids:
|
||||||
|
- NFR-009
|
||||||
|
depends_on:
|
||||||
|
- VR-229
|
||||||
|
summary: Verwijder de externe GitHub-API-rate-limit uit de releasebootstrap en publiceer de actuele productiebuild
|
||||||
|
opnieuw als aantoonbaar immutable private-registryimage.
|
||||||
|
acceptance_criteria:
|
||||||
|
- De uv-bootstrap is op een immutable actioncommit en een exacte uv-versie gepind.
|
||||||
|
- De releaseworkflow gebruikt voor uv-downloads geen GitHub Releases-API die op een gedeeld runner-IP kan limiteren.
|
||||||
|
- Gitea Actions publiceert een private registryimage met digest, SBOM, checksums en release notes.
|
||||||
|
- Unraid is op de gepubliceerde digest gepind en live health/readiness slagen.
|
||||||
|
verification:
|
||||||
|
- uv run pytest tests/unit/test_deployment_configuration.py
|
||||||
|
- ./scripts/codex_verify.sh
|
||||||
|
- Gitea Actions releasebewijs en live Unraid health/readiness
|
||||||
|
primary_paths:
|
||||||
|
- .gitea/workflows/release.yml
|
||||||
|
- tests/unit/test_deployment_configuration.py
|
||||||
|
- docs/audit/FINAL_ACCEPTANCE.md
|
||||||
|
result:
|
||||||
|
completed_at: '2026-08-12'
|
||||||
|
note: Releasebootstrap gepind op immutable setup-uv v8.1.0-commit en uv 0.11.12 via Astral-mirror; Gitea Actions-run
|
||||||
|
3062 publiceerde digest sha256:98f4b33d met checksum-geldige SBOM/release-evidence; Unraid gepind op volledige
|
||||||
|
digest en live health/readiness groen.
|
||||||
|
- id: VR-231
|
||||||
|
title: Sluit de operationele 0.3.17-restpunten
|
||||||
|
status: done
|
||||||
|
priority: P0
|
||||||
|
requirement_ids:
|
||||||
|
- NFR-009
|
||||||
|
depends_on:
|
||||||
|
- VR-230
|
||||||
|
summary: Verwijder de achterhaalde push-to-deployroute, herstel voldoende Docker-netwerkcapaciteit, valideer de
|
||||||
|
resterende mailboxfout en maak actuele versus historische deploymentstatus ondubbelzinnig.
|
||||||
|
acceptance_criteria:
|
||||||
|
- Main-pushes maken geen onbeperkt wachtende deployruns meer; alleen de immutable tagrelease bouwt en publiceert.
|
||||||
|
- Unraid heeft een persistente, conflictvrije default address pool met minstens 256 kleine bridge-netwerken.
|
||||||
|
- De Jobat-productiemailbox slaagt opnieuw via de bestaande OAuth/IMAP-taak of levert een exact extern restpunt.
|
||||||
|
- Projectstatus en deploymentdocumentatie beschrijven alleen het actuele releasepad als operationele waarheid.
|
||||||
|
verification:
|
||||||
|
- uv run pytest tests/unit/test_deployment_configuration.py
|
||||||
|
- ./scripts/codex_verify.sh
|
||||||
|
- Live Docker-netwerk-, mailbox-, container- en readinesssmokes
|
||||||
|
primary_paths:
|
||||||
|
- .gitea/workflows/release.yml
|
||||||
|
- docs/operations/GITEA_DEPLOYMENT.md
|
||||||
|
- docs/operations/UNRAID_DEPLOYMENT.md
|
||||||
|
- docs/ai/PROJECT_STATE.md
|
||||||
|
- tests/unit/test_deployment_configuration.py
|
||||||
|
result:
|
||||||
|
completed_at: '2026-08-12'
|
||||||
|
note: Oude main-pushdeploy verwijderd en run 3065 begrensd geannuleerd; Jobat OAuth/IMAP-herpoll groen met 61
|
||||||
|
duplicates en 0 fouten; Unraid persistent op conflictvrije 10.200.0.0/16→/24-pool gezet en live proefnetwerk
|
||||||
|
bewezen; na Docker-herstart alle vooraf actieve containers hersteld en geen unhealthy containers, VacatureRadar
|
||||||
|
publiek/intern healthy.
|
||||||
|
|||||||
+94
-12
@@ -1,7 +1,7 @@
|
|||||||
# Projectstatus
|
# Projectstatus
|
||||||
|
|
||||||
- Laatst bijgewerkt: 2026-07-29
|
- Laatst bijgewerkt: 2026-08-12
|
||||||
- Repositoryversie: 0.3.14 importperformance
|
- Repositoryversie: 0.3.17 immutable releaseherstel
|
||||||
- Uitvoeringsmodus: autonome backlog
|
- Uitvoeringsmodus: autonome backlog
|
||||||
- Eerste uitvoerbare taak: controleer met `python scripts/backlog.py next`
|
- Eerste uitvoerbare taak: controleer met `python scripts/backlog.py next`
|
||||||
|
|
||||||
@@ -19,6 +19,70 @@ De repository bevat een uitvoerbare Django-MVP met:
|
|||||||
- Celeryworker/scheduler, healthchecks, Docker/Unraidconfiguratie en back-up-/herstelscripts;
|
- Celeryworker/scheduler, healthchecks, Docker/Unraidconfiguratie en back-up-/herstelscripts;
|
||||||
- offline unit-, integratie- en securitytests.
|
- offline unit-, integratie- en securitytests.
|
||||||
|
|
||||||
|
## Laatste documentatie-update
|
||||||
|
|
||||||
|
Visuele demo-hardening `VR-229` op 2026-08-12:
|
||||||
|
|
||||||
|
- iedere publieke demosessie herstelt idempotent zes actuele fictieve vacatures,
|
||||||
|
de laatste scores, twee toekomstige opvolgmomenten en negen geïsoleerde
|
||||||
|
`example.test`-platformmailboxkoppelingen;
|
||||||
|
- mailboxbronnen worden niet meer door de URL-fetchscheduler gepland, de
|
||||||
|
publieke demo wordt uitgesloten van echte mailboxpolling en historische
|
||||||
|
mailboxpolicyfouten vervuilen dashboard en activiteit niet langer;
|
||||||
|
- navigatie en dashboard onderscheiden nu expliciet een actief zoekprofiel van
|
||||||
|
bron- of systeemgezondheid; het lege loginwachtwoordveld heeft geen misleidende
|
||||||
|
mask-placeholder meer;
|
||||||
|
- visuele audit: negen kernroutes groen in dark/light op 390×844, 1280×600,
|
||||||
|
1440×900 en 3440×1440 zonder overflow, heading- of consolefouten;
|
||||||
|
- gerichte regressies: 51 tests groen; volledige gate: 327 tests, 0 failures,
|
||||||
|
0 skips en 84,04% branch-aware dekking;
|
||||||
|
- commit `a6c8a79` en tag `v0.3.16` zijn naar Gitea gepusht; vóór uitrol is onder
|
||||||
|
`/mnt/user/appdata/VacatureRadar/local/backups/release-0.3.16-20260812T094608Z`
|
||||||
|
een checksum-geldige database- en configuratieback-up gemaakt;
|
||||||
|
- Unraid draait healthy zonder restarts op lokaal gebouwde image
|
||||||
|
`sha256:c98001c063e8358dc59dfc175c574187b36021dd5a81f44e3ba982a28c9f9341`;
|
||||||
|
een live sessiesmoke bevestigde dashboard 200, zes demomatches, negen
|
||||||
|
platformmailboxen, actieve ingress, nul oude policyfouten en geen
|
||||||
|
wachtwoord-maskplaceholder;
|
||||||
|
- `VR-230` herstelde de Gitea-releaseworkflow door `setup-uv` op immutable commit
|
||||||
|
`0880764` en uv op 0.11.12 te pinnen; de Astral-mirror verwijdert de eerdere
|
||||||
|
GitHub Releases-API-rate-limit;
|
||||||
|
- vier lege, verweesde Docker-netwerken zijn gecontroleerd verwijderd nadat de
|
||||||
|
runner geen subnet meer kon reserveren; actieve productie- en compose-netwerken
|
||||||
|
bleven onaangeroerd;
|
||||||
|
- Gitea Actions-run 3062 publiceerde 0.3.17 met digest
|
||||||
|
`sha256:98f4b33db353c192967013818f7c82c4f171921f3d300645908d52680fde209f`
|
||||||
|
en artifact `release-evidence-v0.3.17` met SPDX-SBOM, checksums en release notes;
|
||||||
|
- een dedicated `read:package`-token geeft Unraid alleen pulltoegang. De
|
||||||
|
pre-deployback-up staat onder
|
||||||
|
`/mnt/user/appdata/VacatureRadar/local/backups/release-0.3.17-20260812T114510Z`;
|
||||||
|
- Unraid is live op de immutable digest gepind: container healthy, nul restarts,
|
||||||
|
interne liveness/readiness groen, publieke readiness 200 en runtimeversie 0.3.17.
|
||||||
|
- `VR-231` verwijderde de oude `main`-push-to-deployworkflow en annuleerde de
|
||||||
|
laatste onbeperkt wachtende run 3065; alleen semantische tags publiceren nog;
|
||||||
|
- Jobat is via de bestaande OAuth/IMAP-taak geforceerd herhaald: 61 duplicaten
|
||||||
|
correct herkend, nul fouten en de foutstatus volledig gewist;
|
||||||
|
- Unraid gebruikt persistent `10.200.0.0/16,size=24`; een live proefnetwerk kreeg
|
||||||
|
`10.200.1.0/24` en werd daarna verwijderd. Na de gecontroleerde Docker-herstart
|
||||||
|
zijn alle 127 vooraf actieve containers hersteld; VacatureRadar, Grimmory en
|
||||||
|
MeTube zijn healthy en er zijn geen unhealthy of herstartende containers;
|
||||||
|
- serverback-ups bewaren de eerdere `docker.cfg`, de running-containersnapshot,
|
||||||
|
de Grimmory-template en de Gitea-database vóór de begrensde runannulering.
|
||||||
|
Volgende uitvoerbare backlogtaak: geen.
|
||||||
|
|
||||||
|
README-update op 2026-07-30:
|
||||||
|
|
||||||
|
- de repository-ingang benoemt release 0.3.15, de groene 324-testgate, de live
|
||||||
|
M365 app-only OAuth/XOAUTH2-flow en de immutable private Gitea-registryrelease;
|
||||||
|
- eerste configuratie noemt alle negen ondersteunde jobplatformen en verwijst
|
||||||
|
expliciet naar het M365 least-privilege- en rotatiepad in het runbook;
|
||||||
|
- de bestaande negen screenshots zijn visueel herbeoordeeld. De layout is nog
|
||||||
|
actueel; de bronafbeelding toont bewust een gesaneerde demo zonder actieve
|
||||||
|
ingress en is daarom als demo-momentopname verduidelijkt, niet vervangen door
|
||||||
|
een productiebeeld met accountgegevens;
|
||||||
|
- eindverificatie: volledige gate groen met 324 tests, 0 failures, 0 skips en
|
||||||
|
84,07% branch-aware dekking.
|
||||||
|
|
||||||
## Laatste productiehardening
|
## Laatste productiehardening
|
||||||
|
|
||||||
Productieoplevering 0.3.14 op 2026-07-29:
|
Productieoplevering 0.3.14 op 2026-07-29:
|
||||||
@@ -30,9 +94,28 @@ Productieoplevering 0.3.14 op 2026-07-29:
|
|||||||
- de pre-releasecustomdump, media en composeconfig staan checksum-geldig onder `/mnt/user/appdata/VacatureRadar/backups/release-0.3.14-20260729T164500Z`; rollbackimage `vacatureradar:rollback-0.3.13` wijst naar `sha256:34aef8853f77a3f311b119bd67d9f1160a97fd4a21e62c9776222be289bf3b88`;
|
- de pre-releasecustomdump, media en composeconfig staan checksum-geldig onder `/mnt/user/appdata/VacatureRadar/backups/release-0.3.14-20260729T164500Z`; rollbackimage `vacatureradar:rollback-0.3.13` wijst naar `sha256:34aef8853f77a3f311b119bd67d9f1160a97fd4a21e62c9776222be289bf3b88`;
|
||||||
- productie is healthy zonder restarts, versie 0.3.14 is publiek zichtbaar, alle gastkernroutes antwoorden 200 en een echte importsmoke creëerde één job binnen een transactie waarna rollback expliciet bevestigd werd.
|
- productie is healthy zonder restarts, versie 0.3.14 is publiek zichtbaar, alle gastkernroutes antwoorden 200 en een echte importsmoke creëerde één job binnen een transactie waarna rollback expliciet bevestigd werd.
|
||||||
|
|
||||||
Volgende uitvoerbare taak: geen na afronding van VR-228. Alleen de optionele
|
Productieoplevering 0.3.15 op 2026-07-30:
|
||||||
mailbox-/identityconfiguratie (`VR-202`) en publicatie naar een externe private
|
|
||||||
registry (`VR-204`) blijven extern geblokkeerd.
|
- `VR-202` is live afgerond met een dedicated alias, geïsoleerde Exchange-map en
|
||||||
|
inboxregel, app-only OAuth/XOAUTH2 voor IMAP en SMTP en negen platformconnecties;
|
||||||
|
- dezelfde echte testmail werd per connectie eenmaal geïmporteerd en bij herhaling
|
||||||
|
als duplicate herkend; alle ingestelde intervallen zetten `next_poll_at` correct
|
||||||
|
en de scheduler plande geen connectie te vroeg;
|
||||||
|
- de productie-Fernet-sleutel werd live naar een nieuwe primaire MultiFernet-sleutel
|
||||||
|
geroteerd; een tijdelijke versleutelde custom credential bleef ontsleutelbaar en
|
||||||
|
is na de proef verwijderd;
|
||||||
|
- één echte digest werd verzonden en de outbox eindigde op `sent`; productie-logs
|
||||||
|
bevatten geen OAuth-secret, bearer-token of mailpayload;
|
||||||
|
- `VR-204` publiceerde `v0.3.15` naar de private Gitea-registry en Unraid is gepind
|
||||||
|
op `sha256:5a6dc253db7a60c42c0c806ae084ddc6ee7c98db5db71a857423b894a3662ba5`;
|
||||||
|
- commitlabel, digest, SPDX-SBOM, checksums en release notes zijn checksum-geldig
|
||||||
|
opgeslagen als Gitea Actions-artifact `release-evidence-v0.3.15`;
|
||||||
|
- pull, interne en publieke readiness, rollback naar `0.3.14` en herstel naar de
|
||||||
|
immutable `0.3.15`-digest zijn live geslaagd;
|
||||||
|
- lokale en Gitea-releasegate zijn groen met 324 tests, 0 failures, 0 skips en
|
||||||
|
84,05% branch-aware dekking.
|
||||||
|
|
||||||
|
Volgende uitvoerbare taak: geen; alle P0- en P1-releasepunten zijn afgerond.
|
||||||
|
|
||||||
## Laatste risicohardening
|
## Laatste risicohardening
|
||||||
|
|
||||||
@@ -42,8 +125,7 @@ Live mailboxvoorbereiding op 2026-07-30:
|
|||||||
- de alias is niet primair gemaakt en bestaande adressen zijn ongewijzigd gebleven;
|
- de alias is niet primair gemaakt en bestaande adressen zijn ongewijzigd gebleven;
|
||||||
- de Entra-appregistratie, admin consent en Exchange-mailboxscope zijn live ingericht
|
- de Entra-appregistratie, admin consent en Exchange-mailboxscope zijn live ingericht
|
||||||
met app-only OAuth; tijdelijke bootstraprechten en de bootstrap-app zijn na afloop verwijderd;
|
met app-only OAuth; tijdelijke bootstraprechten en de bootstrap-app zijn na afloop verwijderd;
|
||||||
- `VR-202` resteert alleen nog op de productie-uitrol en echte platformalert-/digestproeven;
|
- `VR-202` is volledig afgerond; Basic Auth is niet geactiveerd;
|
||||||
Basic Auth wordt niet geactiveerd;
|
|
||||||
- de volledige lokale gate is groen met 324 tests, 0 failures, 0 skips en 84,05%
|
- de volledige lokale gate is groen met 324 tests, 0 failures, 0 skips en 84,05%
|
||||||
branch-aware dekking.
|
branch-aware dekking.
|
||||||
|
|
||||||
@@ -382,7 +464,10 @@ VR-208-herverificatie op 2026-07-22:
|
|||||||
- De applicatie draait op de Unraid-server via `http://192.168.10.150:1226/` als één Dockerman-container; Supervisor bewaakt intern PostgreSQL 17, Redis, web, worker en scheduler.
|
- De applicatie draait op de Unraid-server via `http://192.168.10.150:1226/` als één Dockerman-container; Supervisor bewaakt intern PostgreSQL 17, Redis, web, worker en scheduler.
|
||||||
- De Dockerman-tegel gebruikt de lokale VacatureRadar-favicon en opent rechtstreeks de WebUI op poort 1226.
|
- De Dockerman-tegel gebruikt de lokale VacatureRadar-favicon en opent rechtstreeks de WebUI op poort 1226.
|
||||||
- De server-side `.env` heeft rechten `0600`; gegenereerde secrets zijn niet naar Git of logs gekopieerd.
|
- De server-side `.env` heeft rechten `0600`; gegenereerde secrets zijn niet naar Git of logs gekopieerd.
|
||||||
- De self-hosted Gitea Actions-runner is niet actief. Deze sessie gebruikte daarom een directe, commitgebonden SSH-deploy; push-to-deploy vereist nog runnerherstel/registratie.
|
- Historisch bewijs (2026-07-22, inmiddels vervangen): de self-hosted runner was
|
||||||
|
toen niet actief en die sessie gebruikte een directe SSH-deploy. Sinds 0.3.17
|
||||||
|
is uitsluitend de taggestuurde immutable registryrelease operationeel; een
|
||||||
|
gewone push naar `main` deployt bewust niet.
|
||||||
- Deze historische LAN-beperking is op 2026-07-29 opgeheven: het publieke domein, reverse proxy/TLS, `DJANGO_DEBUG=0` en secure cookies zijn live gevalideerd.
|
- Deze historische LAN-beperking is op 2026-07-29 opgeheven: het publieke domein, reverse proxy/TLS, `DJANGO_DEBUG=0` en secure cookies zijn live gevalideerd.
|
||||||
|
|
||||||
## Laatste uitgevoerde backlogtaak
|
## Laatste uitgevoerde backlogtaak
|
||||||
@@ -576,16 +661,13 @@ Een toekomstige agent moet altijd de actuele waarheid opnieuw bepalen met:
|
|||||||
|
|
||||||
## Nog te bouwen
|
## Nog te bouwen
|
||||||
|
|
||||||
Momenteel staan er geen `ready` taken meer over; het volgende te behandelen item is `VR-202` als `blocked-external` voor live mailbox-/SMTP-credentials en echte jobalertmails van de gekozen platformen.
|
Momenteel staan er geen `ready` taken of P0/P1-releaseblokkers meer over.
|
||||||
|
|
||||||
## Externe blokkades
|
## Externe blokkades
|
||||||
|
|
||||||
Deze blokkeren onafhankelijke code niet:
|
Deze blokkeren onafhankelijke code niet:
|
||||||
|
|
||||||
- verdere persoonlijke werkgevers-/bronnenkeuze en actuele menselijke review buiten de actieve VITO-/Deliverect-startercatalogus;
|
- verdere persoonlijke werkgevers-/bronnenkeuze en actuele menselijke review buiten de actieve VITO-/Deliverect-startercatalogus;
|
||||||
- live IMAP/SMTP-account en app-password;
|
|
||||||
- containerregistry en credentials;
|
|
||||||
- actieve/voor deze repository geregistreerde Gitea Actions-runner;
|
|
||||||
|
|
||||||
## Bekende verificatiebeperking van de aangeleverde basis
|
## Bekende verificatiebeperking van de aangeleverde basis
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# Finale acceptance 0.3.14
|
# Finale acceptance 0.3.17
|
||||||
|
|
||||||
Status: productie geaccepteerd op 2026-07-29.
|
Status: productie geaccepteerd op 2026-08-12.
|
||||||
|
|
||||||
## Bewijs
|
## Bewijs
|
||||||
|
|
||||||
@@ -14,7 +14,26 @@ Status: productie geaccepteerd op 2026-07-29.
|
|||||||
- PostgreSQL custom dump plus media/configbewijs is naar een geïsoleerde database hersteld; migrations, exacte recordtellingen en een aparte applicatiestart slaagden.
|
- PostgreSQL custom dump plus media/configbewijs is naar een geïsoleerde database hersteld; migrations, exacte recordtellingen en een aparte applicatiestart slaagden.
|
||||||
- Een productie-importsmoke extraheerde en creëerde één geldige job in een expliciete transactie; rollback werd bevestigd en liet geen smokedata achter.
|
- Een productie-importsmoke extraheerde en creëerde één geldige job in een expliciete transactie; rollback werd bevestigd en liet geen smokedata achter.
|
||||||
- De checksum-geldige pre-releaseback-up staat onder `backups/release-0.3.14-20260729T164500Z`; rollbacktag `vacatureradar:rollback-0.3.13` bewaart image `sha256:34aef8853f77a3f311b119bd67d9f1160a97fd4a21e62c9776222be289bf3b88`.
|
- De checksum-geldige pre-releaseback-up staat onder `backups/release-0.3.14-20260729T164500Z`; rollbacktag `vacatureradar:rollback-0.3.13` bewaart image `sha256:34aef8853f77a3f311b119bd67d9f1160a97fd4a21e62c9776222be289bf3b88`.
|
||||||
|
- M365 IMAP en SMTP werken app-only via OAuth/XOAUTH2; negen geïsoleerde
|
||||||
|
platformconnecties importeerden één live testmail idempotent en één testdigest
|
||||||
|
eindigde met outboxstatus `sent`.
|
||||||
|
- Gitea Actions publiceerde `v0.3.15` met immutable digest
|
||||||
|
`sha256:5a6dc253db7a60c42c0c806ae084ddc6ee7c98db5db71a857423b894a3662ba5`.
|
||||||
|
Het artifact bevat een geverifieerde SPDX-SBOM, checksums en release notes.
|
||||||
|
- Unraid is op die digest gepind; pull, health/readiness, rollback naar `0.3.14`
|
||||||
|
en herstel naar `0.3.15` zijn live geslaagd.
|
||||||
|
- Gitea Actions-run 3062 publiceerde `v0.3.17` nadat de releasebootstrap op een
|
||||||
|
immutable `setup-uv`-commit en uv 0.11.12 werd gepind. De uv-download gebruikt
|
||||||
|
de Astral-mirror en vereist geen GitHub Releases-API.
|
||||||
|
- Het checksum-geldige artifact `release-evidence-v0.3.17` bevat image-digest,
|
||||||
|
SPDX-SBOM, checksums en release notes. De gepubliceerde digest is
|
||||||
|
`sha256:98f4b33db353c192967013818f7c82c4f171921f3d300645908d52680fde209f`.
|
||||||
|
- De checksum-geldige pre-deployback-up staat onder
|
||||||
|
`local/backups/release-0.3.17-20260812T114510Z`. Unraid gebruikt een beperkt
|
||||||
|
`read:package`-token en is op de volledige registrydigest gepind.
|
||||||
|
- De 0.3.17-container is healthy met nul restarts; interne liveness en readiness,
|
||||||
|
database, cache en publieke readiness zijn groen en de runtime meldt 0.3.17.
|
||||||
|
|
||||||
## Uitkomst
|
## Uitkomst
|
||||||
|
|
||||||
Er bestaan geen bekende P0- of P1-releaseblokkers. Alleen optionele Entra- en mailboxcredentials blijven extern; zij raken de kernrelease niet.
|
Er bestaan geen bekende P0- of P1-releaseblokkers.
|
||||||
|
|||||||
@@ -1,40 +1,39 @@
|
|||||||
# Gitea push-to-deploy (self-hosted, geen handmatige variabelenvulling)
|
# Gitea immutable releasepad
|
||||||
|
|
||||||
Push naar `main` of `master` triggert automatisch:
|
Een push naar `main` publiceert geen productiecode. Alleen een expliciete semantische
|
||||||
|
tag `v*.*.*` start `.gitea/workflows/release.yml`. De runner:
|
||||||
|
|
||||||
- checkout op de server runner
|
1. verifieert de volledige repository;
|
||||||
- `bash scripts/deploy_docker.sh`
|
2. bouwt de Unraid-image met commit- en versielabels;
|
||||||
- deploy met `docker compose up -d --build`
|
3. publiceert die image naar de private Gitea-containerregistry;
|
||||||
|
4. genereert een SPDX-SBOM, digestbestand, checksums en release notes.
|
||||||
|
|
||||||
Deze workflow gebruikt defaults en hoeft niet te wachten op repo-secrets voor host/ssh/credentials.
|
Productie wordt daarna expliciet op de volledige `versie@sha256:digest` gepind.
|
||||||
|
Hierdoor kan een gewone documentatie- of codepush nooit ongemerkt productie
|
||||||
|
overschrijven en bestaat geen parallel mutable push-to-deploypad meer.
|
||||||
|
|
||||||
## Vereiste eenmalige setup op de server
|
## Vereiste runnerconfiguratie
|
||||||
|
|
||||||
1. Start en registreer een self-hosted Gitea-runner voor deze repo.
|
- De Gitea-runner accepteert label `ubuntu-latest` en kan jobcontainers starten.
|
||||||
2. Zorg dat die runner Docker/Compose kan draaien in de projectmap waar de runner draait.
|
- `REGISTRY_USER` en `REGISTRY_TOKEN` zijn repositorysecrets met uitsluitend de
|
||||||
3. Laat éénmaal dit script draaien op de server:
|
noodzakelijke package-schrijfrechten.
|
||||||
|
- Unraid gebruikt een afzonderlijk `read:package`-token om gepubliceerde images te
|
||||||
|
pullen; dat token staat alleen in de root-Dockerconfig op de server.
|
||||||
|
- De runnerhost heeft voldoende vrije Docker-address-pools voor tijdelijke
|
||||||
|
jobnetwerken; volg daarvoor de netwerksectie in
|
||||||
|
[Unraid-deployment](UNRAID_DEPLOYMENT.md#docker-netwerkcapaciteit).
|
||||||
|
|
||||||
```bash
|
## Publiceren en uitrollen
|
||||||
bash scripts/deploy_docker.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
Daarna is het gedrag volledig push-only:
|
1. Maak en verifieer een database- en configuratieback-up.
|
||||||
|
2. Push de geverifieerde releasecommit naar `main`.
|
||||||
|
3. Maak en push de semantische tag.
|
||||||
|
4. Wacht tot de Gitea-releasejob groen is en verifieer de artifactchecksums.
|
||||||
|
5. Pull op Unraid de gepubliceerde volledige digest.
|
||||||
|
6. Zet `VACATURERADAR_IMAGE` op `versie@sha256:digest` en herstart Compose met
|
||||||
|
`--no-build --force-recreate`.
|
||||||
|
7. Controleer intern en publiek `/health/live/` en `/health/ready/`, runtimeversie,
|
||||||
|
containerhealth en restartteller.
|
||||||
|
|
||||||
- `git push` naar `main` of `master` → automatische deploy
|
PostgreSQL, Redis en Ollama blijven niet publiek bereikbaar. Bewaar de vorige
|
||||||
|
digest totdat minstens één volledige schedulercyclus groen is.
|
||||||
## Defaults
|
|
||||||
|
|
||||||
- `APP_HOST`: hostname van de server (fallback: `hostname`)
|
|
||||||
- `APP_SCHEME`: `http`
|
|
||||||
- `DJANGO_DEBUG`: afgeleid uit `APP_SCHEME`
|
|
||||||
- `0` als `APP_SCHEME=https`
|
|
||||||
- `1` als `APP_SCHEME=http`
|
|
||||||
- `DEPLOY_COMPOSE_FILE`: `docker-compose.yml`
|
|
||||||
- `APP_ORIGINS`, `APP_HOSTS`, `APP_PORT`: optioneel en veilig overnemen als op runner gezet
|
|
||||||
|
|
||||||
## Endpoint na deploy
|
|
||||||
|
|
||||||
- `http://<runner-host>:1226/health/ready/`
|
|
||||||
- `http://<runner-host>:1226/health/live/`
|
|
||||||
|
|
||||||
Voor productie: zet later op de runner de gewenste runtime-omgeving (bijv. reverse proxy + TLS) en herstart push.
|
|
||||||
|
|||||||
@@ -168,6 +168,23 @@ Herstart web, worker en scheduler. Open daarna **Bronnen → Platformmailboxen**
|
|||||||
|
|
||||||
Voor een immutable registryrelease zet je `VACATURERADAR_IMAGE=gitea.itworx.tech/jens/vacatureradar:<versie>@sha256:<digest>` en start je met `docker compose -f docker-compose.unraid.yml up -d --no-build`.
|
Voor een immutable registryrelease zet je `VACATURERADAR_IMAGE=gitea.itworx.tech/jens/vacatureradar:<versie>@sha256:<digest>` en start je met `docker compose -f docker-compose.unraid.yml up -d --no-build`.
|
||||||
|
|
||||||
|
## Docker-netwerkcapaciteit
|
||||||
|
|
||||||
|
Deze Unraid-host draait veel geïsoleerde Compose-netwerken. De ingebouwde
|
||||||
|
Dockerpools delen grote `/16`- en `/20`-netwerken uit en kunnen daardoor opraken
|
||||||
|
terwijl er nog ruim voldoende private adresruimte bestaat. De live host gebruikt
|
||||||
|
daarom persistent in `/boot/config/docker.cfg`:
|
||||||
|
|
||||||
|
```text
|
||||||
|
DOCKER_OPTS="--default-address-pool base=10.200.0.0/16,size=24"
|
||||||
|
```
|
||||||
|
|
||||||
|
Dat levert 256 nieuwe `/24`-netwerken en overlapt niet met LAN `192.168.10.0/24`,
|
||||||
|
WireGuard `10.253.0.0/16` of bestaande Docker-netwerken. De instelling geldt
|
||||||
|
alleen voor nieuw aangemaakte netwerken; bestaande subnets veranderen niet.
|
||||||
|
Maak vóór wijziging een kopie van `docker.cfg`, herstart Docker éénmaal en bewijs
|
||||||
|
de pool met een tijdelijk netwerk dat na inspectie meteen wordt verwijderd.
|
||||||
|
|
||||||
Voor sleutelrotatie zet je `nieuwe-sleutel,oude-sleutel` in `MAILBOX_CREDENTIAL_KEYS`, herstart je de processen en draai je:
|
Voor sleutelrotatie zet je `nieuwe-sleutel,oude-sleutel` in `MAILBOX_CREDENTIAL_KEYS`, herstart je de processen en draai je:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ Django/Celery
|
|||||||
| T-19 | Automatisch solliciteren/massamail | reputatie/privacy/juridisch risico | geen submitendpoint/task, expliciete ADR en non-goal | codezoekreview, AC-043 |
|
| T-19 | Automatisch solliciteren/massamail | reputatie/privacy/juridisch risico | geen submitendpoint/task, expliciete ADR en non-goal | codezoekreview, AC-043 |
|
||||||
| T-20 | Kwaadaardig bestand in sollicitatiedossier | host/browsercompromis | uploads nog niet actief; vóór implementatie allowlist, sniffing, size limit, niet-uitvoerbare opslag vereist | `VR-113`, nieuwe threat review |
|
| T-20 | Kwaadaardig bestand in sollicitatiedossier | host/browsercompromis | uploads nog niet actief; vóór implementatie allowlist, sniffing, size limit, niet-uitvoerbare opslag vereist | `VR-113`, nieuwe threat review |
|
||||||
| T-21 | Back-up bevat secrets/PII | langdurig datalek | encryptie, gescheiden opslag, korte retentie, herstelprocedure | runbook en hersteltest |
|
| T-21 | Back-up bevat secrets/PII | langdurig datalek | encryptie, gescheiden opslag, korte retentie, herstelprocedure | runbook en hersteltest |
|
||||||
| T-22 | Supply-chainpackage/image | code execution | exacte versies/lockfile, beperkte dependencies, pinned baseimages/digests en scans gepland | CI, `VR-115` |
|
| T-22 | Supply-chainpackage/image | code execution | exacte versies/lockfile, beperkte dependencies, pinned baseimages/digests, taggestuurde immutable release als enig productiepad, gescheiden package read/write-tokens en SBOM/checksums | CI, `VR-115`, `VR-230`, `VR-231` |
|
||||||
| T-23 | Onbevoegde admin op thuisnetwerk | volledige datatoegang | uniek wachtwoord, TLS/VPN, geen defaultcredentials, sessiebeveiliging | Unraidchecklist |
|
| T-23 | Onbevoegde admin op thuisnetwerk | volledige datatoegang | uniek wachtwoord, TLS/VPN, geen defaultcredentials, sessiebeveiliging | Unraidchecklist |
|
||||||
| T-24 | Source terms wijzigen | ongewenste voortgezette crawling | reviewdatum, source health en policy expiry gepland | `VR-102`, `VR-112` |
|
| T-24 | Source terms wijzigen | ongewenste voortgezette crawling | reviewdatum, source health en policy expiry gepland | `VR-102`, `VR-112` |
|
||||||
| T-31 | Sollicitatiedossier-export bevat niet-gewenste payload | data-lek of onbedoeld dossieroverdragen | export bevat alleen het gekozen dossier, ZIP-inhoud is beperkt tot snapshot/tijdlijn/print-HTML, bestandsnaam is geslugified zonder padseparators | `tests/integration/test_applications.py` |
|
| T-31 | Sollicitatiedossier-export bevat niet-gewenste payload | data-lek of onbedoeld dossieroverdragen | export bevat alleen het gekozen dossier, ZIP-inhoud is beperkt tot snapshot/tijdlijn/print-HTML, bestandsnaam is geslugified zonder padseparators | `tests/integration/test_applications.py` |
|
||||||
@@ -82,7 +82,7 @@ Django/Celery
|
|||||||
| T-40 | Verouderde lokale CA-store of ongeldige serverketen lokt een TLS-bypass uit | onderschepte vacature- of mailboxdata en credentialdiefstal | expliciet gepinde `certifi`-CA-bundel voor HTTP, robots en IMAP; hostnaamcontrole en `CERT_REQUIRED`; nooit `verify=False`; dependency- en negatieve TLS-tests bij update | `tests/unit/test_tls.py`, `tests/security/test_robots.py`, `apps/sources/services/tls.py`, `VR-201` |
|
| T-40 | Verouderde lokale CA-store of ongeldige serverketen lokt een TLS-bypass uit | onderschepte vacature- of mailboxdata en credentialdiefstal | expliciet gepinde `certifi`-CA-bundel voor HTTP, robots en IMAP; hostnaamcontrole en `CERT_REQUIRED`; nooit `verify=False`; dependency- en negatieve TLS-tests bij update | `tests/unit/test_tls.py`, `tests/security/test_robots.py`, `apps/sources/services/tls.py`, `VR-201` |
|
||||||
| T-41 | Gemodificeerde `next`-parameter na vacaturefeedback | open redirect naar phishingdomein na een geldige gebruikersactie | terugkeer-URL moet dezelfde host en het actuele HTTPS-beleid respecteren; anders veilige detailfallback | `tests/integration/test_tasks_and_more_views.py`, `VR-206` |
|
| T-41 | Gemodificeerde `next`-parameter na vacaturefeedback | open redirect naar phishingdomein na een geldige gebruikersactie | terugkeer-URL moet dezelfde host en het actuele HTTPS-beleid respecteren; anders veilige detailfallback | `tests/integration/test_tasks_and_more_views.py`, `VR-206` |
|
||||||
| T-42 | Gespoofte proxyheaders of ontbrekende publieke hostconfiguratie | host-headerfouten, onjuiste HTTPS-detectie of omzeilde IP-rate-limiting | `PUBLIC_BASE_URL`, exacte host/CSRF-validatie, proxy moet headers overschrijven, forwarded client-IP alleen vanaf `TRUSTED_PROXY_CIDRS` | `tests/unit/test_public_configuration.py`, `tests/unit/test_network.py`, `VR-225` |
|
| T-42 | Gespoofte proxyheaders of ontbrekende publieke hostconfiguratie | host-headerfouten, onjuiste HTTPS-detectie of omzeilde IP-rate-limiting | `PUBLIC_BASE_URL`, exacte host/CSRF-validatie, proxy moet headers overschrijven, forwarded client-IP alleen vanaf `TRUSTED_PROXY_CIDRS` | `tests/unit/test_public_configuration.py`, `tests/unit/test_network.py`, `VR-225` |
|
||||||
| T-43 | Gedeeld demoaccount wijzigt persistente data | bezoekers beïnvloeden elkaars ervaring of echte data | server-side read-only middleware, korte sessie, geen bruikbaar wachtwoord, zichtbare UI-status en productiecheck | `tests/integration/test_auth_login.py`, `tests/unit/test_settings_security.py`, `VR-225` |
|
| T-43 | Gedeeld demoaccount wijzigt persistente data of activeert echte mailboxpolling | bezoekers beïnvloeden elkaars ervaring, echte data of externe accounts | server-side read-only middleware, korte sessie, geen bruikbaar wachtwoord, uitsluitend fictieve `example.test`-mailboxen; de loginseed herstelt begrensd en idempotent en de scheduler sluit de demo-user expliciet uit | `tests/integration/test_auth_login.py`, `tests/integration/test_tasks_and_more_views.py`, `tests/unit/test_settings_security.py`, `VR-225/229` |
|
||||||
|
|
||||||
## Misbruikscenario's
|
## Misbruikscenario's
|
||||||
|
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ Legenda: **I** = geïmplementeerd en getest in de huidige MVP; **P** = gedeeltel
|
|||||||
| NFR-006 | I | offline pytest + coveragegate, inclusief platformbewuste productieconfiguratietests | VR-114 e2e; `tests/unit/test_public_configuration.py`; VR-225/226 |
|
| NFR-006 | I | offline pytest + coveragegate, inclusief platformbewuste productieconfiguratietests | VR-114 e2e; `tests/unit/test_public_configuration.py`; VR-225/226 |
|
||||||
| NFR-007 | P | `scripts/benchmark.py`, `tests/benchmark/test_benchmark.py` | VR-116 |
|
| NFR-007 | P | `scripts/benchmark.py`, `tests/benchmark/test_benchmark.py` | VR-116 |
|
||||||
| NFR-008 | I | volledige negen-schermen-Stitch-cockpit, acht viewports, dark/light, toetsenborddrawer, premium visuele hiërarchie, zelfstandig VacatureRadar-productmerk, lokale ITWorx-ontwikkelaarscredit, toegankelijke interactieve radarillustratie en actuele user-first productdocumentatie | `tests/e2e/test_vr114_browser_and_accessibility.py`, `tests/integration/test_intelligence_views.py`, `tests/integration/test_views.py`, `artifacts/visual-validation/`, negen beelden in `docs/screenshots/readme/`, VR-209–224/226 |
|
| NFR-008 | I | volledige negen-schermen-Stitch-cockpit, acht viewports, dark/light, toetsenborddrawer, premium visuele hiërarchie, zelfstandig VacatureRadar-productmerk, lokale ITWorx-ontwikkelaarscredit, toegankelijke interactieve radarillustratie en actuele user-first productdocumentatie | `tests/e2e/test_vr114_browser_and_accessibility.py`, `tests/integration/test_intelligence_views.py`, `tests/integration/test_views.py`, `artifacts/visual-validation/`, negen beelden in `docs/screenshots/readme/`, VR-209–224/226 |
|
||||||
| NFR-009 | I | lokale SQLite, configureerbare eigenaarbranding, Docker/Unraidconfig, publieke URL-helper, Nginx Proxy Manager-runbook, gevalideerde SSH-deployalias, release-smoke en restore | `tests/unit/test_deployment_configuration.py`, `tests/unit/test_public_configuration.py`, VR-117, VR-217/218/225 afgerond |
|
| NFR-009 | I | lokale SQLite, configureerbare eigenaarbranding, Docker/Unraidconfig, publieke URL-helper, Nginx Proxy Manager-runbook, gevalideerde SSH-deployalias, API-onafhankelijke immutable releasebootstrap zonder parallel push-to-deploypad, begrensde Docker-address pool, release-smoke en restore | `tests/unit/test_deployment_configuration.py`, `tests/unit/test_public_configuration.py`, VR-117, VR-217/218/225/230/231 afgerond |
|
||||||
| NFR-010 | I | `apps/sources/services/health.py`, `apps/sources/tasks.py`, `apps/core/views.py`, `config/settings.py` | `tests/integration/test_source_health.py` | VR-112 observability |
|
| NFR-010 | I | `apps/sources/services/health.py`, `apps/sources/tasks.py`, `apps/core/views.py`, `config/settings.py` | `tests/integration/test_source_health.py` | VR-112 observability |
|
||||||
|
|
||||||
## Systeemacceptatie
|
## Systeemacceptatie
|
||||||
@@ -85,6 +85,9 @@ Iedere backlogtaak die een requirement wijzigt, werkt in dezelfde commit deze ma
|
|||||||
|
|
||||||
## Aanvullende traceability-opmerking
|
## Aanvullende traceability-opmerking
|
||||||
- VR-228 levert release 0.3.14 met een fail-closed disposable PostgreSQL-benchmark, run-lokale importcaches, gebundelde veldprovenance en ongewijzigde score-snapshotcopy. De officiële drie iteraties bewijzen 3.264 queries per 250 items, parser 12/12, dedupeprecision/recall 1,0 en nul rankingchurn; bewijs staat in `docs/audit/IMPORT_PERFORMANCE_*.md` en het machineleesbare artifact.
|
- VR-228 levert release 0.3.14 met een fail-closed disposable PostgreSQL-benchmark, run-lokale importcaches, gebundelde veldprovenance en ongewijzigde score-snapshotcopy. De officiële drie iteraties bewijzen 3.264 queries per 250 items, parser 12/12, dedupeprecision/recall 1,0 en nul rankingchurn; bewijs staat in `docs/audit/IMPORT_PERFORMANCE_*.md` en het machineleesbare artifact.
|
||||||
|
- VR-229 houdt de publieke demo representatief door fictieve vacatures, scores, mailboxkoppelingen en opvolgdatums bij iedere demosessie idempotent te herstellen. Mailboxingress is uitgesloten van URL-fetchplanning en operationele UI-copy onderscheidt een actief profiel van bron- en systeemgezondheid.
|
||||||
|
- VR-230 pint `setup-uv` en uv exact, vermijdt de gelimiteerde GitHub Releases-API via de Astral-mirror en bewijst publicatie plus live pinning van de immutable 0.3.17-registrydigest.
|
||||||
|
- VR-231 verwijdert de hangende mutable deployroute, vergroot de Unraid-netwerkcapaciteit conflictvrij naar 256 `/24`-netwerken en bewijst na een volledige Docker-herstart dat alle vooraf actieve containers en VacatureRadar gezond zijn; de resterende Jobat-poll is opnieuw foutvrij geslaagd.
|
||||||
- VR-227 professionaliseert release 0.3.13 integraal: gecentreerd ultrawideframe, mobiele gastbanner en verticale pipeline, begrensde vacaturepaginering, verplichte zes-viewport-Playwrightgate zonder skips, echte runtimeheartbeat, volledige generieke parserbenchmark en SBOM-generatie. Bewijs staat in `docs/audit/`, de E2E-/auth-/runtime-/adaptertests en `USER_INPUT_REQUIRED.md`.
|
- VR-227 professionaliseert release 0.3.13 integraal: gecentreerd ultrawideframe, mobiele gastbanner en verticale pipeline, begrensde vacaturepaginering, verplichte zes-viewport-Playwrightgate zonder skips, echte runtimeheartbeat, volledige generieke parserbenchmark en SBOM-generatie. Bewijs staat in `docs/audit/`, de E2E-/auth-/runtime-/adaptertests en `USER_INPUT_REQUIRED.md`.
|
||||||
- VR-102 is gerealiseerd met auditbare policyreviews (`SourcePolicyReview`), robotscache met TTL/size/SSRF-controles en fail-closed policy-gating op verlopen of conflicterende reviews.
|
- VR-102 is gerealiseerd met auditbare policyreviews (`SourcePolicyReview`), robotscache met TTL/size/SSRF-controles en fail-closed policy-gating op verlopen of conflicterende reviews.
|
||||||
- VR-107 is gerealiseerd met `apps/sources/views.py`, `apps/sources/services/manual_import.py`, `templates/sources/list.html`, `static/js/manual_import.js` en bijhorende unit/integration-tests.
|
- VR-107 is gerealiseerd met `apps/sources/views.py`, `apps/sources/services/manual_import.py`, `templates/sources/list.html`, `static/js/manual_import.js` en bijhorende unit/integration-tests.
|
||||||
@@ -96,6 +99,12 @@ Iedere backlogtaak die een requirement wijzigt, werkt in dezelfde commit deze ma
|
|||||||
- VR-122 operationaliseert de veilige platformingress als dagelijkse, configureerbare en begrensde mailboxpoll. VDAB-alerts krijgen expliciete provenance, replay blijft idempotent en de bronpagina onderscheidt de werkende jobalertroute van de contractueel afgeschermde officiële Vacature API. Bewijs staat in de e-mailadapter-/importtests, `apps/sources/services/imap_import.py` en ADR-0005.
|
- VR-122 operationaliseert de veilige platformingress als dagelijkse, configureerbare en begrensde mailboxpoll. VDAB-alerts krijgen expliciete provenance, replay blijft idempotent en de bronpagina onderscheidt de werkende jobalertroute van de contractueel afgeschermde officiële Vacature API. Bewijs staat in de e-mailadapter-/importtests, `apps/sources/services/imap_import.py` en ADR-0005.
|
||||||
- VR-123 vervangt die globale poll door gebruikersgescopeerde VDAB- en Indeed-mailboxkoppelingen met versleutelde app-wachtwoorden, afzonderlijke intervallen, per-mailboxleases en UI-beheer. Bewijs staat in `tests/unit/test_mailbox_credentials.py`, `tests/integration/test_mailbox_connections.py`, de migratie en ADR-0009.
|
- VR-123 vervangt die globale poll door gebruikersgescopeerde VDAB- en Indeed-mailboxkoppelingen met versleutelde app-wachtwoorden, afzonderlijke intervallen, per-mailboxleases en UI-beheer. Bewijs staat in `tests/unit/test_mailbox_credentials.py`, `tests/integration/test_mailbox_connections.py`, de migratie en ADR-0009.
|
||||||
- VR-124 breidt dezelfde mailboxingang uit met LinkedIn, ictjob.be, Jobat, StepStone, EuroBrussels en BrusselsJobs. De platformgebonden parser accepteert uitsluitend gelabelde HTTPS-links op het gekozen jobboarddomein; fixtures, adapter-/integratietests en de centrale denylist bewijzen de afscherming.
|
- VR-124 breidt dezelfde mailboxingang uit met LinkedIn, ictjob.be, Jobat, StepStone, EuroBrussels en BrusselsJobs. De platformgebonden parser accepteert uitsluitend gelabelde HTTPS-links op het gekozen jobboarddomein; fixtures, adapter-/integratietests en de centrale denylist bewijzen de afscherming.
|
||||||
|
- VR-202 bewijst de productie-ingress met M365 app-only OAuth/XOAUTH2, een
|
||||||
|
dedicated alias en map, negen platformconnecties, een idempotente live
|
||||||
|
testmail, correcte schedulerstatus en een verzonden digest-outbox.
|
||||||
|
- VR-204 bewijst de immutable releaseketen via de private Gitea-registry,
|
||||||
|
digest-pin, commitlabel, SPDX-SBOM, checksums, release notes en een live
|
||||||
|
Unraid pull-/rollback-/herstelsmoke.
|
||||||
- VR-125 vervangt de twee Brusselse nicheboards door Careerjet, Randstad en Robert Half, retireert buitenregionale werkgevers zonder provenanceverlies en voegt host- en padgebonden adapters toe voor ACA Group, Xplore Group, UHasselt, PXL en ZOL in de kern Hasselt-Genk.
|
- VR-125 vervangt de twee Brusselse nicheboards door Careerjet, Randstad en Robert Half, retireert buitenregionale werkgevers zonder provenanceverlies en voegt host- en padgebonden adapters toe voor ACA Group, Xplore Group, UHasselt, PXL en ZOL in de kern Hasselt-Genk.
|
||||||
- VR-126 herankert profiel en broncatalogus op postcode 2400 Mol en 40 km. De host-, pad- en locatiefilters voor SCK CEN, Cipal Schaubroeck, VanRoey, NTX en Thomas More worden gedekt door regionale adaptertests, source-viewtests, live bronruns en de bijgewerkte threat T-39.
|
- VR-126 herankert profiel en broncatalogus op postcode 2400 Mol en 40 km. De host-, pad- en locatiefilters voor SCK CEN, Cipal Schaubroeck, VanRoey, NTX en Thomas More worden gedekt door regionale adaptertests, source-viewtests, live bronruns en de bijgewerkte threat T-39.
|
||||||
- VR-201 activeert Gemeente Mol en zeven bijkomende Kempen-werkgevers na menselijke voorwaardenreview, fixtures, fail-closed locatie-/routefilters en twee livecycli. Twaalf interessante werkgevers zonder stabiele actieve ingang staan afzonderlijk en niet-scanbaar op de UI-waaklijst. Bewijs staat in de livecatalogus, seeddata, `KempenEmployerAdapter`, bronruns en adapter-/seed-/viewtests.
|
- VR-201 activeert Gemeente Mol en zeven bijkomende Kempen-werkgevers na menselijke voorwaardenreview, fixtures, fail-closed locatie-/routefilters en twee livecycli. Twaalf interessante werkgevers zonder stabiele actieve ingang staan afzonderlijk en niet-scanbaar op de UI-waaklijst. Bewijs staat in de livecatalogus, seeddata, `KempenEmployerAdapter`, bronruns en adapter-/seed-/viewtests.
|
||||||
|
|||||||
+1
-1
@@ -21,7 +21,7 @@ dependencies = [
|
|||||||
"defusedxml==0.7.1",
|
"defusedxml==0.7.1",
|
||||||
"gunicorn==26.0.0",
|
"gunicorn==26.0.0",
|
||||||
"whitenoise==6.11.0",
|
"whitenoise==6.11.0",
|
||||||
"cryptography==49.0.0",
|
"cryptography==50.0.0",
|
||||||
"msal==1.37.0",
|
"msal==1.37.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -48,7 +48,7 @@
|
|||||||
<div class="sidebar-footer">
|
<div class="sidebar-footer">
|
||||||
<div class="sidebar-radar-card">
|
<div class="sidebar-radar-card">
|
||||||
<span class="status-beacon {% if navigation_profile %}is-live{% endif %}" aria-hidden="true"></span>
|
<span class="status-beacon {% if navigation_profile %}is-live{% endif %}" aria-hidden="true"></span>
|
||||||
<span><strong>{% if navigation_profile %}Radar actief{% else %}Profiel nodig{% endif %}</strong><small>{% if navigation_profile %}{{ navigation_profile.name }}{% else %}Configureer je zoekprofiel{% endif %}</small></span>
|
<span><strong>{% if navigation_profile %}Profiel actief{% else %}Profiel nodig{% endif %}</strong><small>{% if navigation_profile %}{{ navigation_profile.name }}{% else %}Configureer je zoekprofiel{% endif %}</small></span>
|
||||||
</div>
|
</div>
|
||||||
{% include "components/developer_credit.html" %}
|
{% include "components/developer_credit.html" %}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
{% block content %}
|
{% block content %}
|
||||||
<header class="cockpit-heading dashboard-heading">
|
<header class="cockpit-heading dashboard-heading">
|
||||||
<div><p class="eyebrow">Intelligence cockpit · vandaag</p><h1>Goedemorgen, {{ user.first_name|default:user.username }}.</h1><p>{{ day_conclusion }}</p></div>
|
<div><p class="eyebrow">Intelligence cockpit · vandaag</p><h1>Goedemorgen, {{ user.first_name|default:user.username }}.</h1><p>{{ day_conclusion }}</p></div>
|
||||||
<div class="radar-status" aria-label="Radarstatus"><span class="status-beacon {% if active_profile %}is-live{% endif %}" aria-hidden="true"></span><span>{% if active_profile %}{{ active_profile.name }} actief{% else %}Geen actief profiel{% endif %}</span>{% if latest_scan %}<span aria-hidden="true">·</span><span>scan {{ latest_scan.started_at|timesince }} geleden</span>{% endif %}</div>
|
<div class="radar-status" aria-label="Profiel- en scanstatus"><span class="status-beacon {% if active_profile %}is-live{% endif %}" aria-hidden="true"></span><span>{% if active_profile %}Profiel actief · {{ active_profile.name }}{% else %}Geen actief profiel{% endif %}</span>{% if latest_scan %}<span aria-hidden="true">·</span><span>laatste URL-scan {{ latest_scan.started_at|timesince }} geleden</span>{% endif %}</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<section class="stats-grid" aria-label="Radar kerncijfers">
|
<section class="stats-grid" aria-label="Radar kerncijfers">
|
||||||
|
|||||||
@@ -56,7 +56,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="input-wrap">
|
<div class="input-wrap">
|
||||||
<input class="auth-input" id="id_password" name="password" type="password"
|
<input class="auth-input" id="id_password" name="password" type="password"
|
||||||
autocomplete="current-password" placeholder="••••••••" required
|
autocomplete="current-password" required
|
||||||
{% if form.errors %}aria-invalid="true"{% endif %}>
|
{% if form.errors %}aria-invalid="true"{% endif %}>
|
||||||
<button class="pw-toggle" type="button" data-pw-toggle aria-label="Wachtwoord tonen">
|
<button class="pw-toggle" type="button" data-pw-toggle aria-label="Wachtwoord tonen">
|
||||||
<svg class="icon" viewBox="0 0 24 24" fill="none" aria-hidden="true" data-eye><path d="M2.5 12s3.5-6 9.5-6 9.5 6 9.5 6-3.5 6-9.5 6-9.5-6-9.5-6Z" stroke="currentColor" stroke-width="1.8"/><circle cx="12" cy="12" r="2.7" stroke="currentColor" stroke-width="1.8"/></svg>
|
<svg class="icon" viewBox="0 0 24 24" fill="none" aria-hidden="true" data-eye><path d="M2.5 12s3.5-6 9.5-6 9.5 6 9.5 6-3.5 6-9.5 6-9.5-6-9.5-6Z" stroke="currentColor" stroke-width="1.8"/><circle cx="12" cy="12" r="2.7" stroke="currentColor" stroke-width="1.8"/></svg>
|
||||||
|
|||||||
@@ -135,15 +135,54 @@ def test_demo_login_logs_in_and_seeds_environment(client):
|
|||||||
# Het gevulde dashboard rendert zonder fouten.
|
# Het gevulde dashboard rendert zonder fouten.
|
||||||
dashboard = client.get(reverse("dashboard:today"))
|
dashboard = client.get(reverse("dashboard:today"))
|
||||||
assert dashboard.status_code == 200
|
assert dashboard.status_code == 200
|
||||||
|
dashboard_html = dashboard.content.decode("utf-8")
|
||||||
|
assert "Profiel actief" in dashboard_html
|
||||||
|
assert "Radar actief" not in dashboard_html
|
||||||
|
assert "Nog geen persoonlijke matches" not in dashboard_html
|
||||||
|
|
||||||
|
sources = client.get(reverse("sources:list"))
|
||||||
|
assert sources.status_code == 200
|
||||||
|
assert "minstens één import actief" in sources.content.decode("utf-8")
|
||||||
|
|
||||||
|
|
||||||
def test_demo_login_is_idempotent(client):
|
def test_demo_login_is_idempotent(client):
|
||||||
from apps.jobs.models import JobPosting
|
from datetime import timedelta
|
||||||
|
|
||||||
|
from django.utils import timezone
|
||||||
|
|
||||||
|
from apps.jobs.models import Application, JobPosting
|
||||||
|
from apps.profiles.models import SearchProfile
|
||||||
|
from apps.sources.models import MailboxConnection, Source
|
||||||
|
|
||||||
client.post(reverse("demo-login"))
|
client.post(reverse("demo-login"))
|
||||||
first = JobPosting.objects.count()
|
first = JobPosting.objects.count()
|
||||||
|
demo_user = get_user_model().objects.get(username=settings.DEMO_USERNAME)
|
||||||
|
profile = SearchProfile.objects.get(user=demo_user, is_active=True)
|
||||||
|
job = profile.scores.order_by("-score").first().job
|
||||||
|
job.status = JobPosting.Status.EXPIRED
|
||||||
|
job.last_seen = timezone.now() - timedelta(days=30)
|
||||||
|
job.save(update_fields=["status", "last_seen"])
|
||||||
|
application = Application.objects.filter(user=demo_user).first()
|
||||||
|
application.follow_up_date = timezone.localdate() - timedelta(days=14)
|
||||||
|
application.save(update_fields=["follow_up_date"])
|
||||||
|
|
||||||
|
client.logout()
|
||||||
client.post(reverse("demo-login"))
|
client.post(reverse("demo-login"))
|
||||||
assert JobPosting.objects.count() == first
|
assert JobPosting.objects.count() == first
|
||||||
|
job.refresh_from_db()
|
||||||
|
application.refresh_from_db()
|
||||||
|
assert job.status == JobPosting.Status.ACTIVE
|
||||||
|
assert job.last_seen.date() == timezone.localdate()
|
||||||
|
assert application.follow_up_date > timezone.localdate()
|
||||||
|
assert profile.scores.count() == 6
|
||||||
|
assert MailboxConnection.objects.filter(user=demo_user, enabled=True).count() == 9
|
||||||
|
assert Source.objects.filter(source_type=Source.Type.EMAIL, failure_count=0).count() >= 9
|
||||||
|
|
||||||
|
|
||||||
|
def test_login_password_field_has_no_mask_placeholder(client):
|
||||||
|
html = client.get(reverse("login")).content.decode("utf-8")
|
||||||
|
assert 'id="id_password"' in html
|
||||||
|
assert 'placeholder="••••••••"' not in html
|
||||||
|
|
||||||
|
|
||||||
def test_demo_account_is_read_only_and_session_is_bounded(client):
|
def test_demo_account_is_read_only_and_session_is_bounded(client):
|
||||||
|
|||||||
@@ -104,6 +104,55 @@ def test_fetch_source_resets_fail_state_on_304(db, source, monkeypatch):
|
|||||||
assert source.failure_count == 0
|
assert source.failure_count == 0
|
||||||
|
|
||||||
|
|
||||||
|
def test_due_source_scheduler_never_schedules_mailbox_sources(db, monkeypatch):
|
||||||
|
from apps.sources.tasks import schedule_due_sources
|
||||||
|
|
||||||
|
email_source = Source.objects.create(
|
||||||
|
name="Mailboxbron",
|
||||||
|
source_type=Source.Type.EMAIL,
|
||||||
|
base_url="",
|
||||||
|
domain="example.mailbox.local",
|
||||||
|
status=Source.Status.ACTIVE,
|
||||||
|
policy=Source.Policy.ALLOW,
|
||||||
|
)
|
||||||
|
scheduled = []
|
||||||
|
monkeypatch.setattr(
|
||||||
|
"apps.sources.tasks.fetch_source.delay", lambda source_id: scheduled.append(source_id)
|
||||||
|
)
|
||||||
|
|
||||||
|
result = schedule_due_sources()
|
||||||
|
|
||||||
|
assert email_source.pk not in scheduled
|
||||||
|
assert result["scheduled"] == len(scheduled)
|
||||||
|
|
||||||
|
|
||||||
|
def test_mailbox_scheduler_excludes_public_demo_user(db, monkeypatch, settings):
|
||||||
|
from django.contrib.auth import get_user_model
|
||||||
|
|
||||||
|
from apps.sources.models import MailboxConnection
|
||||||
|
from apps.sources.tasks import schedule_mailbox_polls
|
||||||
|
|
||||||
|
demo = get_user_model().objects.create_user(username=settings.DEMO_USERNAME)
|
||||||
|
connection = MailboxConnection.objects.create(
|
||||||
|
user=demo,
|
||||||
|
platform="vdab",
|
||||||
|
provider="outlook",
|
||||||
|
username="demo@example.test",
|
||||||
|
mailbox="Demo/VDAB",
|
||||||
|
enabled=True,
|
||||||
|
next_poll_at=timezone.now() - timedelta(minutes=1),
|
||||||
|
)
|
||||||
|
scheduled = []
|
||||||
|
monkeypatch.setattr(
|
||||||
|
"apps.sources.tasks.poll_mailbox.delay",
|
||||||
|
lambda connection_id: scheduled.append(connection_id),
|
||||||
|
)
|
||||||
|
|
||||||
|
schedule_mailbox_polls()
|
||||||
|
|
||||||
|
assert connection.pk not in scheduled
|
||||||
|
|
||||||
|
|
||||||
def test_fetch_source_is_blocked_by_conflicting_review(db):
|
def test_fetch_source_is_blocked_by_conflicting_review(db):
|
||||||
source = Source.objects.create(
|
source = Source.objects.create(
|
||||||
name="Blocked source",
|
name="Blocked source",
|
||||||
|
|||||||
@@ -128,3 +128,19 @@ def test_deploy_uses_selected_environment_file_for_compose() -> None:
|
|||||||
assert 'export VACATURERADAR_ENV_FILE="$env_file"' in deploy_script
|
assert 'export VACATURERADAR_ENV_FILE="$env_file"' in deploy_script
|
||||||
assert 'docker compose --env-file "$env_file" -f "$compose_file" up -d --build' in deploy_script
|
assert 'docker compose --env-file "$env_file" -f "$compose_file" up -d --build' in deploy_script
|
||||||
assert "waarde uit ${env_file}" in deploy_script
|
assert "waarde uit ${env_file}" in deploy_script
|
||||||
|
|
||||||
|
|
||||||
|
def test_release_uv_install_is_immutable_and_does_not_require_github_api() -> None:
|
||||||
|
workflow = (ROOT / ".gitea" / "workflows" / "release.yml").read_text(encoding="utf-8")
|
||||||
|
|
||||||
|
assert "astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0" in workflow
|
||||||
|
assert 'version: "0.11.12"' in workflow
|
||||||
|
assert "astral-sh/setup-uv@v6" not in workflow
|
||||||
|
|
||||||
|
|
||||||
|
def test_release_is_the_only_gitea_deployment_trigger() -> None:
|
||||||
|
workflows = ROOT / ".gitea" / "workflows"
|
||||||
|
|
||||||
|
assert not (workflows / "deploy.yml").exists()
|
||||||
|
release = yaml.safe_load((workflows / "release.yml").read_text(encoding="utf-8"))
|
||||||
|
assert release[True] == {"push": {"tags": ["v*.*.*"]}}
|
||||||
|
|||||||
@@ -141,6 +141,68 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/5a/af/34fee85c48f8d94efc8597bc09470c9dd274c145f1c12e0fbc6ab6d38d74/cffi-2.1.0-cp313-cp313-win_arm64.whl", hash = "sha256:2282cd5e38aa8accd03e99d1256af8411c84cdbee6a89d841b563fdbd1f3e50f", size = 180114, upload-time = "2026-07-06T21:33:22.515Z" },
|
{ url = "https://files.pythonhosted.org/packages/5a/af/34fee85c48f8d94efc8597bc09470c9dd274c145f1c12e0fbc6ab6d38d74/cffi-2.1.0-cp313-cp313-win_arm64.whl", hash = "sha256:2282cd5e38aa8accd03e99d1256af8411c84cdbee6a89d841b563fdbd1f3e50f", size = 180114, upload-time = "2026-07-06T21:33:22.515Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "charset-normalizer"
|
||||||
|
version = "3.5.1"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/e5/3f/143b048436775b0f76ac3eec145c019e8173ccc2885c8f20319b996d5e83/charset_normalizer-3.5.1.tar.gz", hash = "sha256:6117b84ea48435e5356dc737f5121485c30920ba43375fa7b434fd753df0eac3", size = 171764, upload-time = "2026-08-15T08:20:44.807Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/30/27/78873dc8b6a56357517b74b6bb9568b80450e7bb4f6ef7e3fa9d22aa0bd7/charset_normalizer-3.5.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:5b6d1386bf0096d26d3a863dc0a487a5b4eb9aa93cf5ba69683d29dde6b9d60f", size = 344456, upload-time = "2026-08-15T08:17:10.072Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/9a/4c/be49ada26b1f0232d57aa89bbebf997a5cc2332a5616b6eca26ff680044d/charset_normalizer-3.5.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4582c27e8c889d64811987b5967fbd3ae0c823fe1fd933b543d55ac20bb475fa", size = 238530, upload-time = "2026-08-15T08:17:11.563Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/76/84/6f1290fa07ae6978d3960caa3eb1b8019bf9284ab7c2297b00c099ef4250/charset_normalizer-3.5.1-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:1d1c7a53a6c2103925cdd6d7229f8c567379f211c869793df679f2e9f738c369", size = 230200, upload-time = "2026-08-15T08:17:12.919Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e7/a0/47b18adeed31c8f16ba9700f32c1b18594cfa09f47eb672a488c273c22bf/charset_normalizer-3.5.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e6621fb2a4988d6e53eedc455e5903e2679f3967b8acb3d639f1b63c14a2e893", size = 262222, upload-time = "2026-08-15T08:17:14.571Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/38/fe/341861ac118dae06f3ec0eb487488af52128f2ef2faf0b11003944d22259/charset_normalizer-3.5.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7c0c10730342b0c9b35dd1d619beb8214e520bd96a1f870f452680b238aab3e0", size = 258951, upload-time = "2026-08-15T08:17:16.158Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/6f/89/bb5108dc6c3651dca963f2b0a3ba19bbcb370c94e1b6d3e0e844a58e6dca/charset_normalizer-3.5.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b9af956078716df40d985fb0dfeb2c2120c5ca92ba4ff4b388acfd01cdc14d08", size = 248801, upload-time = "2026-08-15T08:17:17.683Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b1/ba/ef83ae3aca816393decfa3530976f38a79812d707b80b580ac33b83f9877/charset_normalizer-3.5.1-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f9f8405c2c758532c74fed975dbee57be1f31a6e865c031870c79a6ed3212ada", size = 244070, upload-time = "2026-08-15T08:17:19.191Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f6/0b/c5292a2462d69b7378ea89793bbb5b2b6fcf6f7dd6d1667f9619094ad553/charset_normalizer-3.5.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:96fef3e886d6a9874b14f27fc193fbdc69d5d8035783d86aa4e1cea594e695f9", size = 240110, upload-time = "2026-08-15T08:17:20.547Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/46/22/111e5be3b740d5c2a5bfcedb3d237b6591e5c2e82ae9d6ffcb121fe0909c/charset_normalizer-3.5.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:5d8531a6569d025f68e2321e7638fb7978f23db58e5f69f56913837aae03816e", size = 232836, upload-time = "2026-08-15T08:17:21.895Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f9/d2/d2aad6fe0dbb44b194bf3becb60f5a0ac48446ade999a47fe7bb41eb09a7/charset_normalizer-3.5.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:aae2ee51122d3ae968a3837d97dc24a0aeebb0dea23694422cd172bd30017cd6", size = 262712, upload-time = "2026-08-15T08:17:23.727Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/35/5a/337e4663a5eae6de99db940ee8066d4145caafb61327db62deda15313cce/charset_normalizer-3.5.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:7235dc28fc6dd9d832ac7c7bce95367dedb85929f17368a0c2bee1e080b9acbf", size = 242977, upload-time = "2026-08-15T08:17:25.157Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ca/85/f82f8a92e31c7519410e2e1afdc630f28ec47490ce2c09a11c1a43cbb459/charset_normalizer-3.5.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:4abdc5f9ad448c1ecbfae2974b820535d6bc6e7eef63babbab3d81cf46968c71", size = 260207, upload-time = "2026-08-15T08:17:26.602Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b7/52/643d11ffd60e9ac2fd1fb87e167a19285b9eefeff4a40e63c87cbfbeab36/charset_normalizer-3.5.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ba501e667c17d8411f98e67a022d9604ef179aff0e459b7e292c796837c13573", size = 250562, upload-time = "2026-08-15T08:17:27.971Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/62/16/46556278c2168d12df9da7fede5dc6fc70e60301b26a82bbeec238c9cfe3/charset_normalizer-3.5.1-cp312-cp312-win32.whl", hash = "sha256:cfa1c0cc3a8f9f53f1243a5a99ac36fd003880199383b37672e86ddda9cb07e2", size = 178507, upload-time = "2026-08-15T08:17:29.277Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/9d/7a/4c6c298171e6b3e745633180ff59350fc0ca0db1ffd28df1e369e0579f71/charset_normalizer-3.5.1-cp312-cp312-win_amd64.whl", hash = "sha256:3617ac3cfd8b9888f145ad89dd6e692285834b0201c6074a5eeaad3fd4d668c2", size = 200551, upload-time = "2026-08-15T08:17:30.668Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/cd/d7/eb95a042f0dd22e304b0b6472b154f3546a1a039a9ee89ccb2a7f61591fc/charset_normalizer-3.5.1-cp312-cp312-win_arm64.whl", hash = "sha256:88e85ab89cb822c1e635f51d6d32e488f94e002e70e2f492bdb8b945543f345a", size = 180700, upload-time = "2026-08-15T08:17:32.028Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/bc/61/2cb6ad133dbbb449fa2d37ccae973232f4827e799af258d15e589a3d1e9e/charset_normalizer-3.5.1-cp313-cp313-android_24_arm64_v8a.whl", hash = "sha256:4f298bdadb8f0b9e5672877f647d1be9373ef5320c9e2f049795e26cad28b6a9", size = 211584, upload-time = "2026-08-15T08:17:33.597Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/18/57/a305c968be1ca13f3dd1b32f445877e97addf55d80b65c7cb35fac82b777/charset_normalizer-3.5.1-cp313-cp313-android_24_x86_64.whl", hash = "sha256:88ca277405c2d3b71c4e1c2ee0e7966e807bcba86a69d11e19ba199d18ae4491", size = 223359, upload-time = "2026-08-15T08:17:35.022Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/09/0a/d3646670292ce8d8f8cc11ac067d44885e697a5591f57a9221128da5e7b3/charset_normalizer-3.5.1-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:9362dd90aa7dab48c0054a21187791ccf05473f7dba5d92b8033ae62164675e7", size = 194464, upload-time = "2026-08-15T08:17:36.452Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/de/93/d51ec556e01042fed6f993ea859311bc7917b466684182fbbceb6ca24762/charset_normalizer-3.5.1-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:977cdbd483a9cff38179bea4fd754289a6f2195c7abd414aba85410b3e66cc5e", size = 197676, upload-time = "2026-08-15T08:17:37.819Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a4/a0/562247944386f7d4ef94467e84876600cc1e0f1b93239aaa9213d2bc3cbd/charset_normalizer-3.5.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e90251c0c7bdd54a100a0dce3c07b7e637278c93af29dbf78ebb89a58c4bac7d", size = 340473, upload-time = "2026-08-15T08:17:39.303Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/31/e7/1d994be1b93d41e9502b8b0460eaa88a1dd8df335df415db87d6c3e91ab2/charset_normalizer-3.5.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:94d78ecec2605a8d0398b0f365d5f12a63248438516f5dac536a5eff7337df4a", size = 240156, upload-time = "2026-08-15T08:17:40.66Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/09/53/27923ce5cc6cbccb832037b27dca98882d9c53e9b69e866bbbef4aae7fc8/charset_normalizer-3.5.1-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:d59b75732e9b6f27388e10c14b0259cc5f2e48c78627d185e6a177b58ad3cffe", size = 228246, upload-time = "2026-08-15T08:17:42.003Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ce/48/5a97e84d63af1d55c07439cb80e56d99a8efb4295700eb4e18c0d1615d2c/charset_normalizer-3.5.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:0d929fc574b4d6fd9e7c0f5c2ede8716a41911923aa7fa5fce38e0818aa4a1ac", size = 263660, upload-time = "2026-08-15T08:17:43.627Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/7a/c2/071575791dcc88316c0a9a65ce38897a82e4cfe4a325f0f7fe1b1ac47bcf/charset_normalizer-3.5.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:394fea06235c8543390050ed5f529187074b029fb027213f6c46ac11ab5d950e", size = 260354, upload-time = "2026-08-15T08:17:45.094Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/fb/af/63240b0c0248c075c2535a1f1bd992821d8251b9f173abc13329661d09e4/charset_normalizer-3.5.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:62b55f6722735a6c472f88361cde6640608773d9443cebdbb51abf436a1fcdd3", size = 250638, upload-time = "2026-08-15T08:17:46.496Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/4d/66/70dfad64f15be09c15ccfee81330a7e515895dbe296dd23114e9a231268a/charset_normalizer-3.5.1-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fa48b1b63d639f9483e0633e092f5851e2348c352f1f9bb6c8182f87884ef876", size = 244583, upload-time = "2026-08-15T08:17:47.963Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c0/24/ef36367d38b9ddd4bccbf72888c342e8de1f5ae506fa0b2dcf970e2732a1/charset_normalizer-3.5.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c71fb0d56c920c269cd3e2e3fe7c610e3f1fdb21a6ce60efa6430ff63676cea6", size = 242038, upload-time = "2026-08-15T08:17:49.481Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/db/ab/55e683ba0fff2e43adafc10daa3001eac90fdaa419a97227d5a7067eedde/charset_normalizer-3.5.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:485a0d363cafefcd2538a73c7c838daa2035f09b2c9f9b5e3133f80c6aeb84c2", size = 233677, upload-time = "2026-08-15T08:17:50.845Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/bd/67/0f40eaf8d1b6e7cf15e82382a2965efaca787fc1c2794b7021d37aaf5036/charset_normalizer-3.5.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:5c0ea61a470e070686aa30892fed79e297d2c8d0ab46b8bcdf027d38c51da591", size = 264491, upload-time = "2026-08-15T08:17:52.61Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/5c/64/12b4c2a11ee8df4fcc518c78b0d93e3a92bd3d5253d1617ce74ff0e8c7ef/charset_normalizer-3.5.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:90b7481fb62fbe172c558bc6fd1c4c98d82004a54a7551f20e11ac9bf0b8708c", size = 245196, upload-time = "2026-08-15T08:17:54.023Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/37/2e/651d910af6d0fba325eee1cda37ec5443462ed25360e666c144166eb6091/charset_normalizer-3.5.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:35fe081843b35aad20ffeccec3eeffbe637b15d14f3fb22cc1b59cd8ec17e93c", size = 261660, upload-time = "2026-08-15T08:17:55.491Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/90/c6/b09e05e6db7f64338e0dc067c79577b1138da86c1e38369096851d96be88/charset_normalizer-3.5.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fd0350afdc3aabd5576f60ea109228bd5538139713c7b094c5cd27c73a98bc6f", size = 252618, upload-time = "2026-08-15T08:17:57.025Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/76/4e/362d4f9fdcdf5556fb2aa3ce7d4a58ebce03ed1ff03aa1d9aca8d02f13f3/charset_normalizer-3.5.1-cp313-cp313-pyemscripten_2025_0_wasm32.whl", hash = "sha256:9d9a0dc7cbe9bec24c3f767c9122c41fe5a1bc43f47cd099d00d393e09769de4", size = 140362, upload-time = "2026-08-15T08:17:58.425Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b4/d4/703be739b26acce318bd29eb3b25b7209e1b1f527f9eae3d1f1f01fdde2b/charset_normalizer-3.5.1-cp313-cp313-win32.whl", hash = "sha256:d63600d620ad0064c3a748b950ac5ea38a80190e5498532efefa4b7b3f1da1f3", size = 177755, upload-time = "2026-08-15T08:18:00.037Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/8a/33/56d97ade41c8db611e727168c52ae46c9224c362ec28d4b65d7e9869e8da/charset_normalizer-3.5.1-cp313-cp313-win_amd64.whl", hash = "sha256:aea996a6aba25260827c9ea511d1addfde2da9eb686ac961838509086188b7e6", size = 199295, upload-time = "2026-08-15T08:18:01.506Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/5b/75/5b20dd1e6573a01a08158fe104104fa2c8abf941745596954185726cd46c/charset_normalizer-3.5.1-cp313-cp313-win_arm64.whl", hash = "sha256:fd0a274c0e5f9a21565cd9d3dd749b61f96b7aa1e20a93aa1ba4029518f2e5c0", size = 179856, upload-time = "2026-08-15T08:18:02.929Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/5b/97/fb4e82231aba271ffd775a1b4993b0defc4e3059f286ae41d9433409fe85/charset_normalizer-3.5.1-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:41876ee62a3dddf48ff1121ad8f0798032aa03f2fd35f21f34a4cab14f18d8d2", size = 331467, upload-time = "2026-08-15T08:19:50.959Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/9f/2f/fe3f187327aac18e2d54e9d2b08e15d27bf9b642d9e51c219f130fc34d1a/charset_normalizer-3.5.1-cp37-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:a6dac12ff6b846103483683f60c5f8fee205121adc58ffd87e90a90a3af69e99", size = 253057, upload-time = "2026-08-15T08:19:52.654Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d7/c7/9e48cee5c161fe24da823b61bf381921d77cb994a0a4de148e95018c1984/charset_normalizer-3.5.1-cp37-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cee5dd7c6fb5dd52a0fe2a740f9bc6e3593f5f8b1788bde49de02086f30182b2", size = 240930, upload-time = "2026-08-15T08:19:54.163Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/49/e0/716601f3cc69be7b198951150c75ead1ece33c3c8036ff6ffa46029659a0/charset_normalizer-3.5.1-cp37-abi3-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:343fb4f2821043bd87095f7b08a1a181febc8e36ac64212143bbfd0a0e1bc235", size = 230822, upload-time = "2026-08-15T08:19:55.807Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d3/05/71bfc5caa0abcc45aea1f6a4d50ac68e59605ddc7666fe8494f4cd229665/charset_normalizer-3.5.1-cp37-abi3-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ae4a097991662cd4fff0ddc74e0fe7874f82e00042fa0ea00855645ed0c79598", size = 260037, upload-time = "2026-08-15T08:19:57.312Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c3/92/de7e32ed05341e7a9c4c877c318418197b7f2d66a3b68d561bf2ac57ca3e/charset_normalizer-3.5.1-cp37-abi3-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4b599739b93b2cbeded49645ae3c8d1405c29ddfbceac1545c87a3f9580a9e96", size = 255097, upload-time = "2026-08-15T08:19:59.056Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f5/7b/ade0a122600319dfa0b1000ab0f9731c94a817904cf3c5de408c73a4ede7/charset_normalizer-3.5.1-cp37-abi3-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b39b69b347e5e47a3b5b8cfc005c68c1ba347474e3960236c4944a8ecd174962", size = 250166, upload-time = "2026-08-15T08:20:00.612Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/75/9c/019fbb9f4834491a160951349b1a3714439376f66e5f7cf18b4f18f0c7aa/charset_normalizer-3.5.1-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:a2028475ba855475b8b4d3cfeb4994269c967aea8b9892dfba907f4263a863a3", size = 241821, upload-time = "2026-08-15T08:20:02.321Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/2b/b8/11d4840bfc99330cc7fbcc2681ee5a044553a6e77655508d8f9b2bff7b34/charset_normalizer-3.5.1-cp37-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:36047af20e17097c3bb9476c2b7655f2f7aa51322c0ba58c07695bedf755a950", size = 232529, upload-time = "2026-08-15T08:20:04.008Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/18/96/2b3a21492d9f65171ac75d872f5018260013d00bfa0ff70ec9f179148cbd/charset_normalizer-3.5.1-cp37-abi3-musllinux_1_2_ppc64le.whl", hash = "sha256:4c4fb141a727957c93edfe5c32a26ceb6b5f6461d67146e2d39f51e16170bea8", size = 260348, upload-time = "2026-08-15T08:20:05.877Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d6/aa/a69a2028e8bd052476c245460ab19d7de595de084dd968f2d75cd50c3e25/charset_normalizer-3.5.1-cp37-abi3-musllinux_1_2_riscv64.whl", hash = "sha256:2f293479cce755c75f1697e87c409b7ae4c555c7dfecb6e988ad13abba943031", size = 247234, upload-time = "2026-08-15T08:20:07.487Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/35/8a/3d130aeabcaf3d2466af76b7b141c08d9e89c9016ab4b7cdd0f7dc2d1c62/charset_normalizer-3.5.1-cp37-abi3-musllinux_1_2_s390x.whl", hash = "sha256:3588e376b3ea2eea84976f67273d679f229e24c66dce7b82ae45aef04ff6e072", size = 256917, upload-time = "2026-08-15T08:20:09.142Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/80/c2/a7379b840292d0c1ab9fbd17d1f3967aa81794dc95bc74be8999d7fedcf7/charset_normalizer-3.5.1-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:e199fb99720074809a7720f1c0b4d919eea8b87e88713e0f8f602f7bef543d9d", size = 254846, upload-time = "2026-08-15T08:20:10.727Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/01/65/d43b714731bb2f40d4053dfa00ecfc1c5a301f8e3316c5db3a09af59fe94/charset_normalizer-3.5.1-cp37-abi3-win32.whl", hash = "sha256:dd732602a7009217f658d5863d12d79d373a4de0eebc111094bcdd3bb8e0a6cc", size = 174216, upload-time = "2026-08-15T08:20:12.334Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/35/4f/b911ed898b26a09789eba9c9200c999aff6c61b4bafaf4838e56d1a1e1a3/charset_normalizer-3.5.1-cp37-abi3-win_amd64.whl", hash = "sha256:70055ff39b97c99e7ae40ea3e393fb62aa2e44dbd9b29f8d14f42fb0025c3959", size = 199764, upload-time = "2026-08-15T08:20:13.908Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f0/a7/920baf467bfd9bf689f3b318340f37aee4572a71f162bd8db51da55ba4fa/charset_normalizer-3.5.1-cp37-abi3-win_arm64.whl", hash = "sha256:87e4f41d375c0b9be2fb5251aee4b8a689169e134535aed81bf085c3b647451e", size = 287318, upload-time = "2026-08-15T08:20:15.551Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/cc/61/d01fc49b8dea277640b55a9e15960dbca9fdc8c9fde18e572d39c59f4019/charset_normalizer-3.5.1-py3-none-any.whl", hash = "sha256:6df0ec430f9a831772c23ca5a224cba36517a58a84bb32c32bb59a9fa67c47f6", size = 68658, upload-time = "2026-08-15T08:20:43.306Z" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "click"
|
name = "click"
|
||||||
version = "8.4.2"
|
version = "8.4.2"
|
||||||
@@ -240,39 +302,39 @@ wheels = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cryptography"
|
name = "cryptography"
|
||||||
version = "49.0.0"
|
version = "50.0.0"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "cffi", marker = "platform_python_implementation != 'PyPy'" },
|
{ name = "cffi", marker = "platform_python_implementation != 'PyPy'" },
|
||||||
]
|
]
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/1f/99/d1c90d6041656cc6ee229dc99cd67fd0cd5aec3c5f7d72fffc27cc750054/cryptography-49.0.0.tar.gz", hash = "sha256:f89660a348f4f78a92366240a61404e337586ef7f5909a2fef59ca88ef505493", size = 854345, upload-time = "2026-06-12T20:02:30.512Z" }
|
sdist = { url = "https://files.pythonhosted.org/packages/de/41/6cbdcf9142d00fe82836fbb51e503e58088575cf7a0fe1dbff6695bf0840/cryptography-50.0.0.tar.gz", hash = "sha256:eeac2acb5a20ed25e0ad6d1df9891a520b78b404266b6d11778f25d5d691a6c9", size = 880201, upload-time = "2026-07-31T14:25:10.11Z" }
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/9b/22/adf66990e63584a68dfb50c24f48a125c07b1699899381c8151e63ed458c/cryptography-49.0.0-cp311-abi3-macosx_11_0_arm64.whl", hash = "sha256:966fe0e9c67490071f14c0d2b1cb2dfb3023c5ce39457343931415f08382f2db", size = 4032100, upload-time = "2026-06-12T20:02:32.143Z" },
|
{ url = "https://files.pythonhosted.org/packages/c5/5c/59086b4aac5e879d38ddbcf74e4be7ade89cebc3eb199a55da998c3bb46a/cryptography-50.0.0-cp311-abi3-macosx_11_0_arm64.whl", hash = "sha256:031e2d5dd4bb9caa3ca9c82e5a197fd8ae680232cee62603d1a813f3f07e3d03", size = 4001252, upload-time = "2026-07-31T14:23:33.331Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/09/41/3797cfaf69cae04a13ee78ebd83f0678d9c02b4779d21ce24445326f1a69/cryptography-49.0.0-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:36d1709f992593689b45bda411498d62c6e365f2ca00b84657d4dadd24de16db", size = 4692978, upload-time = "2026-06-12T20:01:21.305Z" },
|
{ url = "https://files.pythonhosted.org/packages/57/ef/8f2df13c7216bcad3e1c74e07f6e193d93e998e114f524a53877c9af27ad/cryptography-50.0.0-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:fd9192b7b70c573d7f214eb1ae35e00d359f6f5e4b27c7e21e30de1fc6204645", size = 4719554, upload-time = "2026-07-31T14:23:35.611Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/e6/8b/43011f7ebe515a8aa20d61f290a326cd890c2e738e16e59eaff8d9c3a412/cryptography-49.0.0-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0e959b578856a3924bc0cbb710fc12c387b9412a951389f3ca61704a9e25f325", size = 4716422, upload-time = "2026-06-12T20:01:48.566Z" },
|
{ url = "https://files.pythonhosted.org/packages/d9/41/029086c34d91052fc3b88bcc8056f709a7c915c7a23b235a54eb800b1c97/cryptography-50.0.0-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:06a32a980526a6ab9a4b9bf8f7385800791e2bb960903cb6b530e4817509a3b7", size = 4702130, upload-time = "2026-07-31T14:23:37.635Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/4a/91/01ce7303a4579e6d3a6abef01bd322848e9ea7a219adcabc5048b9033571/cryptography-49.0.0-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:53ecee2e23f7169b6117e99fc8a944e5e50f79e69758a83b52a00cb98ab2b2d2", size = 4700503, upload-time = "2026-06-12T20:02:47.091Z" },
|
{ url = "https://files.pythonhosted.org/packages/7d/ff/b6ce0954962e7f7b969f850a883744197bb3910bdfd7b6da162eab7d9f68/cryptography-50.0.0-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:a1b30560f2acc95aa8b2e06e716a13dbfc97314747b80d9707e307f77b40d6b3", size = 4725244, upload-time = "2026-07-31T14:23:39.471Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/62/99/a2c95cf8293f07491e9e27c20cc4dcd18176d944e674679adeb1d0173fd6/cryptography-49.0.0-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:2eda353d8a27bcbcaa4cbed18994a74ab4d19a2ca897db188ea269ab9b71419b", size = 5309779, upload-time = "2026-06-12T20:02:08.987Z" },
|
{ url = "https://files.pythonhosted.org/packages/06/1e/63a1027cb7fec360a182208e1b7767d5aa1fe57be3d6aa856e69a321edc0/cryptography-50.0.0-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:8d89f3976b10b4ce31118de72329025f70d2c6ead14a8217c5514dd2c6d5a78f", size = 5342265, upload-time = "2026-07-31T14:23:41.286Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/20/2c/0622f20ff02b2ef32558733443805dc82fd4c275be01b2d19d14676f3a1b/cryptography-49.0.0-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:2afe9051da7ae7bd5905da5a949280c7d2bb75682e188f650a9d0f2756b834c6", size = 4749683, upload-time = "2026-06-12T20:02:03.335Z" },
|
{ url = "https://files.pythonhosted.org/packages/6b/72/a1116d683a6d7ece94590013882515de087edf9ef0e6292aae615a44df73/cryptography-50.0.0-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:b42a28c1844fd9de8f3f7d540e36b66f3a9c83fceac7170ebc7a6a19edd9dcae", size = 4734609, upload-time = "2026-07-31T14:23:43.139Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/a3/5b/c5246635d5fd3b64e0d45ae10e99fd32fe9676a79915ccfe5a61ba9af1a5/cryptography-49.0.0-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:0b82e28ee398a386f0807bba7884d30f25218855690f45115831bcce5d90822c", size = 4337874, upload-time = "2026-06-12T20:02:54.323Z" },
|
{ url = "https://files.pythonhosted.org/packages/15/37/36a9c479bbe49acea2636c7fd3360d20f7b7e079c300352011c44850b181/cryptography-50.0.0-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:900131fafd8aead39ac7dd3a7e833be754c17a95cfd91221636949fe4eb0aa8a", size = 4356517, upload-time = "2026-07-31T14:23:44.939Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/6d/88/05563c7fe2e914e87d1a536d06fe83e66b4e1d95cb593e05aea375531da8/cryptography-49.0.0-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:ccac2bfebc306b862133e3bb71f3f6ee8bb525240089b2d952e4144b3a6d5da7", size = 4700283, upload-time = "2026-06-12T20:01:34.822Z" },
|
{ url = "https://files.pythonhosted.org/packages/32/98/8a151d64367204cbc63ec65d37502f1d9c53cf4bfc6ec3c532614dbec60d/cryptography-50.0.0-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:07949c449a1abcf60d1ee6e88956d89404c7df3c8258f46589e912988e551987", size = 4724529, upload-time = "2026-07-31T14:23:46.93Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/c4/b6/d7696e4e890d6ae1469935164c9e5215c557671cb78d6e3f458ccceaa632/cryptography-49.0.0-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:d0527ce944105f257f605a827d6ebead966c752038b6e8656abb9c5edee6fc68", size = 5265844, upload-time = "2026-06-12T20:01:24.09Z" },
|
{ url = "https://files.pythonhosted.org/packages/22/f6/ec13b470172126464a86bf54d2294a46d29837fc51ba3e45d4047946fb5e/cryptography-50.0.0-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:f89831ef99dd7dd169ab06d63a831adb9e20a87aac6d380266bbda5823349169", size = 5299852, upload-time = "2026-07-31T14:23:48.851Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/a9/3c/f3ad17eecc1a57b0ba236dc01f90e783c51f4a2f35f64777cc4f47a184b2/cryptography-49.0.0-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:cbc77da8c523d5abd028635ba850a6966fcee2c82e2bf65a41d1d8afe0f98be9", size = 4749290, upload-time = "2026-06-12T20:01:30.848Z" },
|
{ url = "https://files.pythonhosted.org/packages/da/3a/f05e32c99d440c9bb891ea0e36c9091891e36be5a9a87ab2ee6ea20729f6/cryptography-50.0.0-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:82148ec5bddac30b51a5b3c1945075f896fa022cb93f8e4a01e9f6ee95292c5f", size = 4734462, upload-time = "2026-07-31T14:23:50.861Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/4f/01/339573cf1023163a400b0b5d16f6d507de413b9f60be6fd1b77feeaf6737/cryptography-49.0.0-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:b87e65d263b3e5d3bb92a57e2a6638e2f31110fa7aa890c7b2dbba42248d0a3f", size = 4834612, upload-time = "2026-06-12T20:01:29.246Z" },
|
{ url = "https://files.pythonhosted.org/packages/ca/dc/bd72b26be8953f80625f63151efd38eee71c76ca6cf591c08ff34615a79e/cryptography-50.0.0-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:1489e263a8048bb8b6a8bac662eb2d402ea5d2b7b4699b72f385f1e2772db105", size = 4852708, upload-time = "2026-07-31T14:23:52.715Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/71/fd/577302e213a1be9468f92d1afef66fcf1ef83d516819d9992ca547f592bd/cryptography-49.0.0-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:66ec79c3904820572d7e987abdf304281f141d37ad9a489b8e97066e7b9b6459", size = 4980804, upload-time = "2026-06-12T20:01:42.853Z" },
|
{ url = "https://files.pythonhosted.org/packages/27/20/c930314a2ab476d15dec966ec87e2e9637bb02b06106b12c0396c57bb603/cryptography-50.0.0-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:7cec5b856506da6defb290f30c9ee687d5f5e8cb0bd3f6459dde43b0b4fa40ef", size = 5004179, upload-time = "2026-07-31T14:23:54.887Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/1f/09/f42b1d190c5ba75f72062a387f8030d1d75f6ab035788f1d9c4b01de6525/cryptography-49.0.0-cp311-abi3-win_amd64.whl", hash = "sha256:e5dfc1e64de5677cec922ffa8da89c546d0415bf6efdf081842e5d44c84e1f0e", size = 3810026, upload-time = "2026-06-12T20:02:39.262Z" },
|
{ url = "https://files.pythonhosted.org/packages/32/2e/c9db68a0c4bfa28e310707527c0ee3a2bd254104d2e02e68f368e197aa4c/cryptography-50.0.0-cp311-abi3-win_amd64.whl", hash = "sha256:bd1c592e4d5974f0d08d4888e432157adba757c66da0246918e43677fafa2d30", size = 3840395, upload-time = "2026-07-31T14:23:56.677Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/19/2a/5bb823f5bedcf80718cea7fbc95ec5515cca3769633c4b01a32be7f30e7c/cryptography-49.0.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:ec5e529fb80935c94fe7b729f9972b50e351a0e6b50aa294fd5cabb109fcc29a", size = 4025947, upload-time = "2026-06-12T20:01:25.745Z" },
|
{ url = "https://files.pythonhosted.org/packages/03/37/73d005be173aff344af30e9fd2a576575cb2391a7101d9cd3842e1fa8cce/cryptography-50.0.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:ccdc4a71a4dabae05de219404f9f4abc38e3b58422177ff93d0da05967dafa07", size = 4036009, upload-time = "2026-07-31T14:24:24.122Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/3d/df/40577043ca124e17012f408ddddaeb213b856336ac82ddb3bc915f39e29f/cryptography-49.0.0-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f78ff2c9ed8dc2d036b0f4d640e22522213d047c1b14e61205a7e55c80a494d4", size = 4692429, upload-time = "2026-06-12T20:01:53.628Z" },
|
{ url = "https://files.pythonhosted.org/packages/ff/c6/7a6202a534e32103a285b7834a120869557fe198d51d7cfe59754c8bda9c/cryptography-50.0.0-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:910e1d2668e7de9648f2bcee30e180db2a6b15c30f887d7c4c93ddf96e3992e3", size = 4745252, upload-time = "2026-07-31T14:24:26.118Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/2c/99/2d13299eb3dd27b02dcfaafcc91d6b5cb3329f7cbd6d8f51921acd566c1a/cryptography-49.0.0-cp39-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:35b151772baff2c74cba7fa290ceaff4c3b11c0c881eb93eb5dbc05a7cfbba18", size = 4700968, upload-time = "2026-06-12T20:02:45.383Z" },
|
{ url = "https://files.pythonhosted.org/packages/85/4f/0fa8c2f4428198f15d9ff8d63400e27afbf94ce833f6108da1eb3753f945/cryptography-50.0.0-cp39-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a91296cb61e8df6f86d0c19cc4068228da256bf59bf86049fbd821084565327f", size = 4728939, upload-time = "2026-07-31T14:24:27.994Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/a5/4d/9c0cd02f95e2602dd5e563da149ee0830abef3537be8b34dc56281ebe27a/cryptography-49.0.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:0f21641cf4b30fca7aee061ced0ec7ad7b073518088b7c9969a297c0ae796c69", size = 4697758, upload-time = "2026-06-12T20:01:41.13Z" },
|
{ url = "https://files.pythonhosted.org/packages/d1/63/54dd723490ba2dc09b299682c10b38db38f159728bcaae8c591b8af2f22d/cryptography-50.0.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:e722f16708d854fe924790e051061f6704a472c3bac347b6fd88033ea8dd0dc5", size = 4748483, upload-time = "2026-07-31T14:24:30.254Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/24/01/186c825898477d77e2324d5360fefe622ff1d8d1963ec0554e2cada8ec77/cryptography-49.0.0-cp39-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:9e82dcc8e56052715fb18b2429e3bca4823b1629136a2084fc45a9a5cecb9b64", size = 5298863, upload-time = "2026-06-12T20:02:24.579Z" },
|
{ url = "https://files.pythonhosted.org/packages/1d/dd/7c77d26285cc7f6991efce64a0f5b4f9383bfa5dd8c5033003eaf7db4cdb/cryptography-50.0.0-cp39-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:d764dcf130c428ef66786f866dd750f53182bc608813489915e9fc106bb0c82f", size = 5367599, upload-time = "2026-07-31T14:24:32.457Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/b8/7b/62cbbab75d0659865bf0273790031544a0b16c8072d258f9428dcd8190dc/cryptography-49.0.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:6f2debedf9ca60cf1d5bd466475638af5130f89965605cd818484d19987d3a21", size = 4735983, upload-time = "2026-06-12T20:01:50.14Z" },
|
{ url = "https://files.pythonhosted.org/packages/46/c9/f60aed34c013f317f92817b6c171c2d22a78270fa41109bd4b08af26b194/cryptography-50.0.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:105110f43a471dbd0060b9c9516cb8a6a79233631a04cc2ba16f28323ac6e025", size = 4762647, upload-time = "2026-07-31T14:24:34.599Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/6c/72/3e798c064bc39e471008075d0f9bc9daf77a80879c092e4a8e170c585ed4/cryptography-49.0.0-cp39-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:8c25ceb16df5b9435f3f6a9829204985b0e0cbee3b48aacd432c7d2c850b44d9", size = 4334173, upload-time = "2026-06-12T20:01:44.743Z" },
|
{ url = "https://files.pythonhosted.org/packages/be/f3/f9a0173b139372c3a48ed98154b45cc6b9de17c789d5ab552e621c293609/cryptography-50.0.0-cp39-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:828743d939e9629bc267b8e2d08d8bb67cd4319c771a33d4b18b22dd8fb7440a", size = 4385197, upload-time = "2026-07-31T14:24:36.647Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/f0/ee/6fca21d1ac73e06f8bef71940abfd4d2f6472b4bca284d770f32bd4086f6/cryptography-49.0.0-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:28d8b15e6275f12c8a207dc309dfa957903c927d08d0cc937ee3f63f200693cc", size = 4697298, upload-time = "2026-06-12T20:02:20.918Z" },
|
{ url = "https://files.pythonhosted.org/packages/d8/36/83bb81f6e569bc38e1e4a7bc80f29b46bb9601920bc455fc8e888f5d5742/cryptography-50.0.0-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:2a8183b489dc1f7f80f135780fadc1108f14b31b8a40411c7a5b17425f65f28b", size = 4748095, upload-time = "2026-07-31T14:24:39.493Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/67/d0/a5fcd3515f0bae49a7b6d0413cc1bdccdcc1fc0047037a0d480642cdc5d6/cryptography-49.0.0-cp39-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:6fc361c34fb6aac015ce19435876635e5c6d21db31998b0920f675f131e043b8", size = 5254338, upload-time = "2026-06-12T20:02:22.737Z" },
|
{ url = "https://files.pythonhosted.org/packages/6b/16/d3008eff98c764979865834c3d386d4fd041b5f52e7f34fc29ac1a5eb515/cryptography-50.0.0-cp39-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:6e7d61120573a7f2cd94cc095f9e81f6967c61ccdf194285aa143ecec8e0b708", size = 5325948, upload-time = "2026-07-31T14:24:41.556Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/a0/84/84fe36f19caf857d61cb7fc9c63035a47ffabd84ea12d1d393148efa3615/cryptography-49.0.0-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:2400ef9c9e2299a25614eb1dea3db54a69b1349efd043bfac9c67630d136df36", size = 4735650, upload-time = "2026-06-12T20:02:41.389Z" },
|
{ url = "https://files.pythonhosted.org/packages/9c/f8/d97f9603efda3888187bfdb893f26c41be4735c10631d05d284ee6b047c4/cryptography-50.0.0-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:37fdb0d0111f1e2ff07139dfb79f1b49531f8e213c46f1163dd7642979b58c47", size = 4762400, upload-time = "2026-07-31T14:24:43.636Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/6c/a0/db537264e234f7273a73ec020873d6d6b39dfd8a53db78b550ca8320440e/cryptography-49.0.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:67e1d20ad9ef3a563c59ef22e7a8a0b8210bd26604369ea4a30a7c66aefe504e", size = 4834820, upload-time = "2026-06-12T20:01:51.847Z" },
|
{ url = "https://files.pythonhosted.org/packages/64/a2/4615c8f7d81a00b1d6e6afe19f694e1543582349fb5f4076f6cb5dc36485/cryptography-50.0.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:c87f62a3d3b9888ed0fdde100ec06aa61ca9cd44bad9057d1dff9a516b5f5bb9", size = 4878208, upload-time = "2026-07-31T14:24:45.522Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/93/77/8df9eb486495979bccecd1062e2eaf435250e84437040295b57d09048b0b/cryptography-49.0.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:42b0684e0e40cf26122427802486f6d93aea593612603a94fbf260c7eb1e9c1b", size = 4967968, upload-time = "2026-06-12T20:02:12.524Z" },
|
{ url = "https://files.pythonhosted.org/packages/d2/1a/efcfb02f91407149a0dacffffab791f7e19bf6385f63b3666dc8b5e5c9c8/cryptography-50.0.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:65c2c3add92b45fd0709db8594536aea39c2a67af0e27ffcf049c498501140b7", size = 5037050, upload-time = "2026-07-31T14:24:47.697Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/c2/e6/f60198ea8d9dfa15fff9ed4ca02ce362f6eadd9ba757dcc50634c4257b63/cryptography-49.0.0-cp39-abi3-win_amd64.whl", hash = "sha256:026ac7423e6fa66872d3bf889be5974507da3944f866f704fa200eadacd00001", size = 3785547, upload-time = "2026-06-12T20:02:26.847Z" },
|
{ url = "https://files.pythonhosted.org/packages/57/30/4a22984d4f1bdfb8c054f07a92bc176b97a3134cc1d6c4b3bffb1f3688b4/cryptography-50.0.0-cp39-abi3-win_amd64.whl", hash = "sha256:d24fead1d4d076e1bfb006dcec392074a3cd8d7b4fc8a595aa64073b2b7a96ba", size = 3874135, upload-time = "2026-07-31T14:24:50.085Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -469,6 +531,20 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/d1/53/70eb8c5c6037f27448f1e3c54ebede9545a801ae63f0a7254afca4fe8e45/lxml-6.1.1-cp313-cp313-win_arm64.whl", hash = "sha256:424aa57aca0897eb922aef34395bd1289b3b6f04e6bae20ea123c0c7e333cffc", size = 3658490, upload-time = "2026-05-19T19:22:53.846Z" },
|
{ url = "https://files.pythonhosted.org/packages/d1/53/70eb8c5c6037f27448f1e3c54ebede9545a801ae63f0a7254afca4fe8e45/lxml-6.1.1-cp313-cp313-win_arm64.whl", hash = "sha256:424aa57aca0897eb922aef34395bd1289b3b6f04e6bae20ea123c0c7e333cffc", size = 3658490, upload-time = "2026-05-19T19:22:53.846Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "msal"
|
||||||
|
version = "1.37.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "cryptography" },
|
||||||
|
{ name = "pyjwt", extra = ["crypto"] },
|
||||||
|
{ name = "requests" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/9a/99/d840198ecf6e8057bbc937f129ae940404485d736cda73253bbff9537f01/msal-1.37.0.tar.gz", hash = "sha256:1b1672a33ee467c1d70b341bb16cafd51bb3c817147a95b93263794b03971bec", size = 182444, upload-time = "2026-05-29T19:49:05.561Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/94/b0/d807279f4b55d16d1f120d5ac4344c6e39b56732e2a224d40bded7fd67ad/msal-1.37.0-py3-none-any.whl", hash = "sha256:dd17e95a7c71bce75e8108113438ba7c4a086b3bcad4f57a8c09b7af3d753c2d", size = 123725, upload-time = "2026-05-29T19:49:04.335Z" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "packaging"
|
name = "packaging"
|
||||||
version = "26.2"
|
version = "26.2"
|
||||||
@@ -595,6 +671,20 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151, upload-time = "2026-03-29T13:29:30.038Z" },
|
{ url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151, upload-time = "2026-03-29T13:29:30.038Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pyjwt"
|
||||||
|
version = "2.13.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/3b/81/58d0ac84e1ef3a3843791d6954d94c0b33d526c75eeb1efbce9d0a4c4077/pyjwt-2.13.0.tar.gz", hash = "sha256:41571c89ca91598c79e8ef18a2d07367d4810fbbd6f637794879baf1b7703423", size = 107515, upload-time = "2026-05-21T19:54:36.618Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a3/5e/ecf12fdb62546d64385c158514e9b2b671f7832108ef2ecd2020ce0af2d1/pyjwt-2.13.0-py3-none-any.whl", hash = "sha256:66adcc2aff09b3f1bbd95fc1e1577df8ac8723c978552fd43304c8a290ac5728", size = 31274, upload-time = "2026-05-21T19:54:35.362Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.optional-dependencies]
|
||||||
|
crypto = [
|
||||||
|
{ name = "cryptography" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pytest"
|
name = "pytest"
|
||||||
version = "9.0.2"
|
version = "9.0.2"
|
||||||
@@ -686,6 +776,21 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/e8/02/89e2ed7e85db6c93dfa9e8f691c5087df4e3551ab39081a4d7c6d1f90e05/redis-6.4.0-py3-none-any.whl", hash = "sha256:f0544fa9604264e9464cdf4814e7d4830f74b165d52f2a330a760a88dd248b7f", size = 279847, upload-time = "2025-08-07T08:10:09.84Z" },
|
{ url = "https://files.pythonhosted.org/packages/e8/02/89e2ed7e85db6c93dfa9e8f691c5087df4e3551ab39081a4d7c6d1f90e05/redis-6.4.0-py3-none-any.whl", hash = "sha256:f0544fa9604264e9464cdf4814e7d4830f74b165d52f2a330a760a88dd248b7f", size = 279847, upload-time = "2025-08-07T08:10:09.84Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "requests"
|
||||||
|
version = "2.34.2"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "certifi" },
|
||||||
|
{ name = "charset-normalizer" },
|
||||||
|
{ name = "idna" },
|
||||||
|
{ name = "urllib3" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/ac/c3/e2a2b89f2d3e2179abd6d00ebd70bff6273f37fb3e0cc209f48b39d00cbf/requests-2.34.2.tar.gz", hash = "sha256:f288924cae4e29463698d6d60bc6a4da69c89185ad1e0bcc4104f584e960b9ed", size = 142856, upload-time = "2026-05-14T19:25:27.735Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a0/f4/c67b0b3f1b9245e8d266f0f112c500d50e5b4e83cb6f3b71b6528104182a/requests-2.34.2-py3-none-any.whl", hash = "sha256:2a0d60c172f83ac6ab31e4554906c0f3b3588d37b5cb939b1c061f4907e278e0", size = 73075, upload-time = "2026-05-14T19:25:26.443Z" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ruff"
|
name = "ruff"
|
||||||
version = "0.12.7"
|
version = "0.12.7"
|
||||||
@@ -774,6 +879,15 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/9e/a4/017a7a6cbe387d961a688ec31364ae60a5c4e22c96ae9921b79a947c855d/tzlocal-5.4.4-py3-none-any.whl", hash = "sha256:aae09f0126a8a86fa736be266eb4a471380d26a0de3bc14844e7821fee3e2a15", size = 18115, upload-time = "2026-06-29T08:03:38.666Z" },
|
{ url = "https://files.pythonhosted.org/packages/9e/a4/017a7a6cbe387d961a688ec31364ae60a5c4e22c96ae9921b79a947c855d/tzlocal-5.4.4-py3-none-any.whl", hash = "sha256:aae09f0126a8a86fa736be266eb4a471380d26a0de3bc14844e7821fee3e2a15", size = 18115, upload-time = "2026-06-29T08:03:38.666Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "urllib3"
|
||||||
|
version = "2.7.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/53/0c/06f8b233b8fd13b9e5ee11424ef85419ba0d8ba0b3138bf360be2ff56953/urllib3-2.7.0.tar.gz", hash = "sha256:231e0ec3b63ceb14667c67be60f2f2c40a518cb38b03af60abc813da26505f4c", size = 433602, upload-time = "2026-05-07T16:13:18.596Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/7f/3e/5db95bcf282c52709639744ca2a8b149baccf648e39c8cc87553df9eae0c/urllib3-2.7.0-py3-none-any.whl", hash = "sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897", size = 131087, upload-time = "2026-05-07T16:13:17.151Z" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "vacatureradar"
|
name = "vacatureradar"
|
||||||
version = "0.3.14"
|
version = "0.3.14"
|
||||||
@@ -790,6 +904,7 @@ dependencies = [
|
|||||||
{ name = "gunicorn" },
|
{ name = "gunicorn" },
|
||||||
{ name = "httpx" },
|
{ name = "httpx" },
|
||||||
{ name = "lxml" },
|
{ name = "lxml" },
|
||||||
|
{ name = "msal" },
|
||||||
{ name = "psycopg", extra = ["binary"] },
|
{ name = "psycopg", extra = ["binary"] },
|
||||||
{ name = "python-dateutil" },
|
{ name = "python-dateutil" },
|
||||||
{ name = "pyyaml" },
|
{ name = "pyyaml" },
|
||||||
@@ -821,13 +936,14 @@ requires-dist = [
|
|||||||
{ name = "bleach", specifier = "==6.4.0" },
|
{ name = "bleach", specifier = "==6.4.0" },
|
||||||
{ name = "celery", extras = ["redis"], specifier = "==5.6.3" },
|
{ name = "celery", extras = ["redis"], specifier = "==5.6.3" },
|
||||||
{ name = "certifi", specifier = "==2026.6.17" },
|
{ name = "certifi", specifier = "==2026.6.17" },
|
||||||
{ name = "cryptography", specifier = "==49.0.0" },
|
{ name = "cryptography", specifier = "==50.0.0" },
|
||||||
{ name = "defusedxml", specifier = "==0.7.1" },
|
{ name = "defusedxml", specifier = "==0.7.1" },
|
||||||
{ name = "django", specifier = "==5.2.16" },
|
{ name = "django", specifier = "==5.2.16" },
|
||||||
{ name = "feedparser", specifier = "==6.0.12" },
|
{ name = "feedparser", specifier = "==6.0.12" },
|
||||||
{ name = "gunicorn", specifier = "==26.0.0" },
|
{ name = "gunicorn", specifier = "==26.0.0" },
|
||||||
{ name = "httpx", specifier = "==0.28.1" },
|
{ name = "httpx", specifier = "==0.28.1" },
|
||||||
{ name = "lxml", specifier = "==6.1.1" },
|
{ name = "lxml", specifier = "==6.1.1" },
|
||||||
|
{ name = "msal", specifier = "==1.37.0" },
|
||||||
{ name = "playwright", marker = "extra == 'browser'", specifier = "==1.54.0" },
|
{ name = "playwright", marker = "extra == 'browser'", specifier = "==1.54.0" },
|
||||||
{ name = "psycopg", extras = ["binary"], specifier = "==3.3.4" },
|
{ name = "psycopg", extras = ["binary"], specifier = "==3.3.4" },
|
||||||
{ name = "pytest", marker = "extra == 'dev'", specifier = "==9.0.2" },
|
{ name = "pytest", marker = "extra == 'dev'", specifier = "==9.0.2" },
|
||||||
|
|||||||
Reference in New Issue
Block a user