Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
f61c4f1
Introduce BFF service with Flask app, OAuth, and OpenAPI support
srozen Mar 3, 2026
265ab47
Add `.env.example` template and update `.gitignore`
srozen Mar 3, 2026
992f38d
Add monorepo versioning orchestrator
srozen Mar 4, 2026
972ff4e
Add BFF architecture reference links to README
srozen Mar 4, 2026
b7e33c4
Add workflow to build and publish BFF Docker image
srozen Mar 4, 2026
8d0c952
Update Python and dependency requirements; remove Redis
srozen Mar 4, 2026
638c8d9
Add environment validation and test coverage for `load_settings_from_…
srozen Mar 5, 2026
2b1ce98
Update README: agnosticize BFF title
srozen Mar 5, 2026
45d35c9
Update README to use shorthand flag `-h` in versioning script example
srozen Mar 5, 2026
c1f6d0b
Merge branch 'backend-for-frontend' of github.com:n-side-dev/wefa int…
srozen Mar 5, 2026
d463ce4
Merge branch 'main' into backend-for-frontend
srozen Mar 6, 2026
292b88f
Make container port explicit in Dockerfile; set default to 5000
srozen Mar 6, 2026
9bf8c44
Update OpenAPI spec paths in README for consistency with folder struc…
srozen Mar 6, 2026
7053303
Merge branch 'main' of github.com:n-side-dev/wefa into backend-for-fr…
srozen Mar 6, 2026
00f51d1
Merge branch 'backend-for-frontend' of github.com:n-side-dev/wefa int…
srozen Mar 6, 2026
297a659
Filter hop-by-hop and sensitive headers in proxy requests
srozen Mar 6, 2026
023f281
Refactor OAuth callback handling to improve session management and er…
srozen Mar 6, 2026
183d48a
Add support for configurable backend proxy timeouts
srozen Mar 6, 2026
9f906ce
Improve userinfo proxy to handle errors, timeouts, and missing tokens
srozen Mar 6, 2026
b231dd3
Replace `print` statements with structured logging, add timeout asser…
srozen Mar 6, 2026
15e6a00
Handle logout timeouts and missing tokens gracefully
srozen Mar 6, 2026
d7b96e6
Set default container port to 5000 in docker-compose configuration
srozen Mar 6, 2026
4a1f082
Encrypt session tokens to enhance security
srozen Mar 6, 2026
bee82c3
Revert cookie encryption in a first phase
srozen Mar 6, 2026
4425fc0
Encrypt OAuth tokens into HttpOnly cookies and update session handling
srozen Mar 9, 2026
03ee9e1
Merge branch 'main' into backend-for-frontend
srozen Mar 11, 2026
090a707
Merge branch 'backend-for-frontend' into version-orchestrator
srozen Mar 11, 2026
3ff67b7
Merge branch 'main' of github.com:n-side-dev/wefa into version-orches…
srozen Mar 11, 2026
dc9228d
Update release workflow to use GHCR and improve BFF Docker image publ…
srozen Mar 11, 2026
b5a6d0f
Add SemVer and PEP 440 compatibility for version management
srozen Mar 11, 2026
13cafe2
Normalize prerelease labels to lowercase and enhance SemVer validatio…
srozen Mar 11, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 36 additions & 4 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,40 @@ permissions:
contents: read

jobs:
validate-version:
name: Validate monorepo version
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6

- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: '3.12'

- name: Normalize release tag for SemVer validation
id: normalize-tag
run: |
raw_tag="${{ github.event.release.tag_name }}"
if [[ "$raw_tag" =~ ^[vV](.+)$ ]]; then
semver_tag="${BASH_REMATCH[1]}"
else
semver_tag="$raw_tag"
fi
echo "semver_tag=$semver_tag" >> "$GITHUB_OUTPUT"

- name: Validate shared version against release tag
run: python3 scripts/wefa_version.py check --expect "${{ steps.normalize-tag.outputs.semver_tag }}"

publish-bff-image:
name: Build, tag and publish BFF image
if: ${{ !github.event.release.draft }}
runs-on: ubuntu-latest
needs: validate-version
permissions:
contents: read
packages: write
steps:
- name: Checkout
uses: actions/checkout@v6
Expand All @@ -21,15 +51,15 @@ jobs:
- name: Login to registry
uses: docker/login-action@v3
with:
registry: ${{ secrets.OCI_REGISTRY_HOST }}
username: ${{ secrets.OCI_REGISTRY_USERNAME }}
password: ${{ secrets.OCI_REGISTRY_PASSWORD }}
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Compute image metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ secrets.OCI_REGISTRY_HOST }}/${{ secrets.OCI_BFF_IMAGE_REPOSITORY }}
images: ghcr.io/${{ github.repository }}/bff
tags: |
type=raw,value=${{ github.event.release.tag_name }}
type=raw,value=latest,enable=${{ !github.event.release.prerelease }}
Expand All @@ -46,6 +76,7 @@ jobs:
publish-npm:
name: Publish Vue package to npm
runs-on: ubuntu-latest
needs: validate-version
permissions:
id-token: write
defaults:
Expand Down Expand Up @@ -82,6 +113,7 @@ jobs:
publish-pypi:
name: Publish Django package to PyPI
runs-on: ubuntu-latest
needs: validate-version
environment:
name: pypi
url: https://pypi.org/p/nside-wefa
Expand Down
29 changes: 29 additions & 0 deletions .github/workflows/scripts.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
name: Scripts CI

on:
pull_request:
paths:
- 'scripts/**'
- '.github/workflows/scripts.yml'
push:
branches: [ main, develop ]
paths:
- 'scripts/**'
- '.github/workflows/scripts.yml'
workflow_dispatch:

jobs:
tests:
name: Scripts Tests
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6

- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: '3.12'

- name: Run scripts unit tests
run: python3 -m unittest discover -s scripts -p 'test_*.py'
26 changes: 26 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,32 @@ See the [Vue README](vue/README.md) for build scripts, Storybook, and component

The demo apps in both workspaces illustrate how to compose the packages together.

## Release Versioning

This monorepo uses one shared version across `vue/`, `django/`, and `bff/`.
Use the root orchestrator script for any version change:

```bash
python3 scripts/wefa_version.py -h
python3 scripts/wefa_version.py show
python3 scripts/wefa_version.py check --expect <x.y.z[-prerelease]>
python3 scripts/wefa_version.py bump patch
python3 scripts/wefa_version.py set 1.0.0-rc.1
```

Use `--dry-run` to preview changes and `--allow-dirty-version-files` only when you intentionally need to override preflight checks.

Use SemVer for CLI inputs and release tags (for example `1.2.3-rc.1`). For prereleases, only
`alpha.<N>`, `beta.<N>`, and `rc.<N>` are supported. Python project files are written in PEP 440
equivalent form (`1.2.3a1`, `1.2.3b1`, `1.2.3rc1`) by the orchestrator.

### BFF Container Image

When a GitHub release is published, CI builds and pushes the BFF Docker image to GitHub Container Registry (GHCR):

- `ghcr.io/n-side-dev/wefa/bff:<release-tag>`
- `ghcr.io/n-side-dev/wefa/bff:latest` for non-prerelease tags only

## Contributing

Contributions are welcome! Start with open issues or propose new ideas through GitHub discussions. Please read [Django CONTRIBUTE](django/CONTRIBUTE.md) and/or [Vue CONTRIBUTE](vue/CONTRIBUTE.md) for the current contribution workflow.
Expand Down
3 changes: 2 additions & 1 deletion django/CONTRIBUTE.md
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,7 @@ When making changes:
- Update the main README if adding new features
- Keep examples up to date
- Update version numbers as needed
- For monorepo releases, run version bumps from the repository root with `python3 scripts/wefa_version.py`

### API Documentation

Expand All @@ -354,4 +355,4 @@ If you have questions or need help:
3. Create a new issue with detailed information
4. Tag maintainers if urgent

Thank you for contributing to N-SIDE WeFa!
Thank you for contributing to N-SIDE WeFa!
104 changes: 104 additions & 0 deletions scripts/test_wefa_version.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import argparse
import importlib.util
import sys
import tempfile
from pathlib import Path
from unittest import TestCase, mock


MODULE_PATH = Path(__file__).with_name("wefa_version.py")
SPEC = importlib.util.spec_from_file_location("wefa_version_module", MODULE_PATH)
assert SPEC and SPEC.loader
wefa_version = importlib.util.module_from_spec(SPEC)
sys.modules[SPEC.name] = wefa_version
SPEC.loader.exec_module(wefa_version)


MIXED_RC_VERSIONS = {
"vue/package.json": "1.0.0-rc.1",
"vue/package-lock.json": "1.0.0-rc.1",
"django/pyproject.toml": "1.0.0rc1",
"django/uv.lock": "1.0.0rc1",
"django/nside_wefa/__init__.py": "1.0.0rc1",
"bff/pyproject.toml": "1.0.0rc1",
"bff/uv.lock": "1.0.0rc1",
}


class WefaVersionTests(TestCase):
def test_semver_to_pep440_conversion(self) -> None:
self.assertEqual(
wefa_version.semver_to_python_version("1.2.3-rc.4", flag_name="version"),
"1.2.3rc4",
)
self.assertEqual(
wefa_version.semver_to_python_version("1.2.3-alpha.2", flag_name="version"),
"1.2.3a2",
)
self.assertEqual(
wefa_version.semver_to_python_version("1.2.3-beta.7", flag_name="version"),
"1.2.3b7",
)

def test_pep440_to_semver_conversion(self) -> None:
self.assertEqual(
wefa_version.pep440_to_semver("1.2.3rc4", flag_name="version"),
"1.2.3-rc.4",
)
self.assertEqual(
wefa_version.pep440_to_semver("1.2.3a2", flag_name="version"),
"1.2.3-alpha.2",
)
self.assertEqual(
wefa_version.pep440_to_semver("1.2.3b7", flag_name="version"),
"1.2.3-beta.7",
)

def test_rejects_unsupported_semver_prerelease_label(self) -> None:
with self.assertRaisesRegex(ValueError, "prerelease label must be one of"):
wefa_version.build_version_targets("1.2.3-preview.1", flag_name="version")

def test_supported_prerelease_label_is_canonicalized_to_lowercase(self) -> None:
targets = wefa_version.build_version_targets("1.2.3-RC.1", flag_name="version")
self.assertEqual(targets.semver, "1.2.3-rc.1")
self.assertEqual(targets.python, "1.2.3rc1")

def test_unified_version_accepts_mixed_semver_and_pep440(self) -> None:
self.assertEqual(wefa_version.unified_version(MIXED_RC_VERSIONS), "1.0.0-rc.1")

def test_check_expect_semver_matches_pep440_python_sources(self) -> None:
args = argparse.Namespace(expect="1.0.0-rc.1")
with mock.patch.object(wefa_version, "collect_versions", return_value=MIXED_RC_VERSIONS):
self.assertEqual(wefa_version.cmd_check(args), 0)

def test_post_update_assertions_require_expected_serialization(self) -> None:
targets = wefa_version.build_version_targets("1.0.0-rc.1", flag_name="version")

with mock.patch.object(wefa_version, "collect_versions", return_value=MIXED_RC_VERSIONS):
wefa_version.post_update_assertions(targets)

wrong_versions = dict(MIXED_RC_VERSIONS)
wrong_versions["django/pyproject.toml"] = "1.0.0-rc.1"
with mock.patch.object(wefa_version, "collect_versions", return_value=wrong_versions):
with self.assertRaisesRegex(RuntimeError, "expected 1.0.0rc1"):
wefa_version.post_update_assertions(targets)

def test_transactional_update_restores_files_on_failure(self) -> None:
with tempfile.TemporaryDirectory() as tmpdir:
path = Path(tmpdir) / "version.txt"
path.write_text("before\n", encoding="utf-8")
source = wefa_version.VersionSource(
path=path,
reader=lambda p: p.read_text(encoding="utf-8").strip(),
version_format=wefa_version.VERSION_FORMAT_SEMVER,
)

def mutate() -> None:
path.write_text("after\n", encoding="utf-8")
raise RuntimeError("boom")

with mock.patch.object(wefa_version, "VERSION_SOURCES", (source,)):
with self.assertRaisesRegex(RuntimeError, "restored tracked version files"):
wefa_version.run_transactional_version_update(mutate)

self.assertEqual(path.read_text(encoding="utf-8"), "before\n")
Loading
Loading