Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
de03aa0
reworked oauth implementation for better structuring
leon1995 Sep 5, 2025
b5d6837
optimizations + docker files
leon1995 Sep 9, 2025
c08fe44
minor improvemnts
leon1995 Sep 11, 2025
abc55c9
logging + one port docker
leon1995 Sep 11, 2025
924530e
docker oneport + requirements without hashes
leon1995 Sep 11, 2025
c1265fa
fixes
leon1995 Sep 12, 2025
22281f3
Refactor Docker setup: remove Caddyfile, update docker-compose for si…
leon1995 Sep 12, 2025
ca46b43
more debugging
leon1995 Sep 12, 2025
e1b057b
Add redirect to main page after successful OAuth authentication
leon1995 Sep 12, 2025
8ab0898
fix redirect paths
leon1995 Sep 12, 2025
36077eb
Refactor CSV download functionality to use async methods and improve …
leon1995 Sep 12, 2025
a37116c
Add docstrings to components and functions for better clarity; refact…
leon1995 Sep 21, 2025
660ac14
Update CHANGELOG and README for Docker support and CI/CD enhancements…
leon1995 Sep 21, 2025
8c79c60
Fix formatting issues in Caddyfile, docker-compose.yaml, README.md, a…
leon1995 Sep 21, 2025
1c1e0ce
fix
leon1995 Sep 21, 2025
4634932
Update Docker build workflow to change SHA tag prefix from branch nam…
leon1995 Sep 21, 2025
f216b95
do not test incompatable python versions
leon1995 Sep 21, 2025
cd24961
Refactor OAuth session handling to ensure error state is reset correc…
leon1995 Sep 21, 2025
6f82419
Enhance Docker build workflow by adding a wait mechanism for build co…
leon1995 Sep 21, 2025
9d76fc1
lint
leon1995 Sep 21, 2025
fe45a3a
Fix logical condition in release workflow for prerelease tagging; upd…
leon1995 Sep 21, 2025
bec37dd
Improve team filtering logic in working time verification page; add c…
leon1995 Sep 21, 2025
f20b5b4
Refactor release workflow to enhance run detection logic; increase pe…
leon1995 Sep 21, 2025
2d8b114
lint
leon1995 Sep 21, 2025
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
6 changes: 6 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Ignore all hidden files and folders
.*

# But keep these specific hidden files/folders
!.web/bun.lockb
!.web/package.json
2 changes: 1 addition & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ jobs:

strategy:
matrix:
python-version: [ "3.11", "3.12", "3.13" ]
python-version: [ "3.13" ]
os: [ ubuntu-latest, windows-latest, macos-latest ]
distribution: [ "${{ needs.build.outputs.WHL }}",
"${{ needs.build.outputs.TARGZ }}" ]
Expand Down
97 changes: 97 additions & 0 deletions .github/workflows/docker-build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
name: Build and Push Docker Image

on:
workflow_call:
inputs:
image_tag:
description: 'Docker image tag'
required: true
type: string
is_release:
description: 'Whether this is a release build'
required: false
type: boolean
default: false
push:
branches:
- main
pull_request:
branches:
- main
types: [opened, synchronize, reopened]

env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}

jobs:
build-and-push:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write

steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Log in to Container Registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Set image tag
id: tag
run: |
if [ "${{ github.event_name }}" = "workflow_call" ]; then
echo "tag=${{ inputs.image_tag }}" >> $GITHUB_OUTPUT
echo "is_release=${{ inputs.is_release }}" >> $GITHUB_OUTPUT
# Extract version without 'v' prefix for release builds
if [ "${{ inputs.is_release }}" = "true" ]; then
VERSION_WITHOUT_V=${inputs.image_tag#v}
echo "version_without_v=$VERSION_WITHOUT_V" >> $GITHUB_OUTPUT
fi
elif [ "${{ github.ref }}" = "refs/heads/main" ]; then
echo "tag=dev" >> $GITHUB_OUTPUT
echo "is_release=false" >> $GITHUB_OUTPUT
else
echo "tag=pr-${{ github.event.number }}" >> $GITHUB_OUTPUT
echo "is_release=false" >> $GITHUB_OUTPUT
fi

- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=ref,event=branch
type=ref,event=pr
type=sha,prefix=sha-
type=raw,value=${{ steps.tag.outputs.tag }},enable=${{ steps.tag.outputs.is_release == 'true' }}
type=raw,value=${{ steps.tag.outputs.version_without_v }},enable=${{ steps.tag.outputs.is_release == 'true' }}
type=raw,value=latest,enable=${{ steps.tag.outputs.is_release == 'true' }}

- name: Build and push Docker image
uses: docker/build-push-action@v5
with:
context: .
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max

- name: Output image details
run: |
echo "Built and pushed images:"
echo "${{ steps.meta.outputs.tags }}"
echo ""
echo "Image digest:"
echo "${{ steps.meta.outputs.json }}"
192 changes: 142 additions & 50 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -1,68 +1,160 @@
name: Release package
name: Release Docker Image

on:
push:
tags:
- "v[0-9]+.[0-9]+.[0-9]+" # normal release
- "v[0-9]+.[0-9]+.[0-9]+rc[0-9]+" # release candidate
- "v[0-9]+.[0-9]+.[0-9]+[ab][0-9]+" # alpha or beta release
- 'v*'
workflow_dispatch:
inputs:
version:
description: 'Version to release (e.g., v1.0.0)'
required: true
type: string

jobs:
build:
uses: ./.github/workflows/build.yml

upload:
release:
runs-on: ubuntu-latest
needs: build
outputs:
DO_GITHUB_RELEASE: ${{ steps.detect-release.outputs.DO_GITHUB_RELEASE }}
permissions:
contents: read
packages: write

steps:
- name: Download Artifact
uses: actions/download-artifact@v4
- name: Checkout repository
uses: actions/checkout@v4

- name: Extract version from tag
id: version
run: |
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
VERSION="${{ github.event.inputs.version }}"
else
VERSION="${{ github.ref_name }}"
fi
# Remove 'v' prefix if present
VERSION=${VERSION#v}
echo "version=$VERSION" >> $GITHUB_OUTPUT
echo "tag=v$VERSION" >> $GITHUB_OUTPUT
echo "Released version: $VERSION"

- name: Call Docker Build Workflow
id: docker-build
uses: actions/github-script@v7
with:
name: distributions
path: dist
script: |
const { data } = await github.rest.actions.createWorkflowDispatch({
owner: context.repo.owner,
repo: context.repo.repo,
workflow_id: 'docker-build.yml',
ref: context.ref,
inputs: {
image_tag: '${{ steps.version.outputs.tag }}',
is_release: 'true'
}
});
console.log('Docker build workflow triggered for release');
return data;

- name: Publish package to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
- name: Wait for Docker build to complete
uses: actions/github-script@v7
with:
password: ${{ secrets.PYPI_API_TOKEN }}
- name: Detect release version
id: detect-release
run: |
do_github_release=$((echo "${GITHUB_REF}" | grep -Eq "^refs\/tags\/v[0-9]+\.[0-9]+\.[0-9]+(rc[0-9]+)?$") && echo 1 || echo 0)
echo DO_GITHUB_RELEASE=$do_github_release >> $GITHUB_OUTPUT
echo DO_GITHUB_RELEASE=$do_github_release
script: |
const maxWaitTime = 10 * 60 * 1000; // 10 minutes
const checkInterval = 30 * 1000; // 30 seconds
const startTime = Date.now();

publish:
runs-on: ubuntu-latest
needs: upload
if: needs.upload.outputs.DO_GITHUB_RELEASE == '1'
permissions:
contents: write
console.log('Waiting for Docker build workflow to complete...');

steps:
- name: Check out Git repository
uses: actions/checkout@v4
while (Date.now() - startTime < maxWaitTime) {
// Get the most recent workflow runs for docker-build.yml
const { data: runs } = await github.rest.actions.listWorkflowRuns({
owner: context.repo.owner,
repo: context.repo.repo,
workflow_id: 'docker-build.yml',
per_page: 10
});

- name: Download Build
uses: actions/download-artifact@v4
with:
name: distributions
path: dist
// Find the run that was triggered by our dispatch
// Look for runs within the last 5 minutes that are workflow_dispatch events
const recentRuns = runs.filter(run =>
run.event === 'workflow_dispatch' &&
run.created_at > new Date(Date.now() - 5 * 60 * 1000).toISOString()
);

- name: Detect prerelease
console.log(`Found ${recentRuns.length} recent workflow_dispatch runs`);

// Find the most recent run that matches our criteria
const ourRun = recentRuns.find(run => {
// Check if this run has the expected inputs (is_release: true)
// We can't directly access inputs from the API, so we'll use heuristics
// Look for runs that are either in progress or recently completed
return run.status === 'in_progress' ||
run.status === 'queued' ||
(run.status === 'completed' && run.conclusion);
});

if (!ourRun) {
// If no active run found, check if there's a recently completed run
const completedRun = recentRuns.find(run => run.status === 'completed');
if (completedRun) {
console.log(`Found completed run ${completedRun.id} with conclusion: ${completedRun.conclusion}`);
if (completedRun.conclusion === 'success') {
console.log('Docker build completed successfully');
break;
} else {
throw new Error(`Docker build failed with conclusion: ${completedRun.conclusion}`);
}
} else {
console.log('No matching workflow run found, assuming completed');
break;
}
} else {
console.log(`Workflow run ${ourRun.id} status: ${ourRun.status}, conclusion: ${ourRun.conclusion || 'pending'}`);

if (ourRun.status === 'completed') {
if (ourRun.conclusion === 'success') {
console.log('Docker build completed successfully');
break;
} else {
throw new Error(`Docker build failed with conclusion: ${ourRun.conclusion}`);
}
}
}

console.log(`Waiting ${checkInterval / 1000} seconds before next check...`);
await new Promise(resolve => setTimeout(resolve, checkInterval));
}

if (Date.now() - startTime >= maxWaitTime) {
throw new Error('Timeout waiting for Docker build to complete');
}

- name: Create GitHub Release
if: github.event_name == 'push'
run: |
do_prerelease=$((echo "${GITHUB_REF}" | grep -Eq "^refs\/tags\/v[0-9]+\.[0-9]+\.[0-9]+rc[0-9]+$") && echo 1 || echo 0)
echo DO_PRERELEASE=$do_prerelease >> $GITHUB_ENV
echo DO_PRERELEASE=$do_prerelease
gh release create "${{ steps.version.outputs.tag }}" \
--title "Release ${{ steps.version.outputs.tag }}" \
--notes "## Docker Image

- name: Attach artifacts to github release
uses: softprops/action-gh-release@v2
with:
files: |
dist/*.whl
CHANGELOG.md
prerelease: ${{ env.DO_PRERELEASE == '1' }}
body_path: CHANGELOG.md
The Docker image has been built and pushed to GitHub Container Registry:

\`\`\`bash
docker pull ghcr.io/${{ github.repository }}:${{ steps.version.outputs.tag }}
\`\`\`

### Available Tags
- \`${{ steps.version.outputs.tag }}\` - Specific version
- \`${{ steps.version.outputs.version }}\` - Version without 'v' prefix
- \`latest\` - Latest release (if this is the default branch)

### Multi-Architecture Support
This image supports both \`linux/amd64\` and \`linux/arm64\` architectures." \
${{ (contains(steps.version.outputs.tag, 'alpha') || contains(steps.version.outputs.tag, 'beta') || contains(steps.version.outputs.tag, 'rc')) && '--prerelease' || '' }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

- name: Output release details
run: |
echo "Release details:"
echo "Version: ${{ steps.version.outputs.version }}"
echo "Tag: ${{ steps.version.outputs.tag }}"
echo "Docker image: ghcr.io/${{ github.repository }}:${{ steps.version.outputs.tag }}"
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ celerybeat.pid

# Environments
.env
.env.production
.venv
env/
venv/
Expand Down
18 changes: 16 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,23 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0)

## [Unreleased]

## [v3.0.0] -
### Added

- **Docker Support**: Complete containerization with multi-architecture support (AMD64 + ARM64)
- **CI/CD Pipeline**: Automated GitHub Actions workflows for testing, building, and deployment
- **GitHub Container Registry**: Automated Docker image publishing to `ghcr.io/leon1995/fwtv`
- **Multi-Platform Testing**: Automated testing across Python 3.11, 3.12, 3.13 on Linux, Windows, and macOS
- **Release Automation**: Automated release creation with versioned Docker images
- **Development Docker Images**: Pre-built development images tagged as `dev`
- **Release Docker Images**: Versioned images with tags like `v1.0.0`, `1.0.0`, and `latest`

### Changed

### Changed app to run in the browser using reflex
- **Deployment**: Added Docker-first deployment option for easier production setup
- **Documentation**: Comprehensive README update with Docker usage instructions
- **Workflow Optimization**: Optimized release pipeline to reuse Docker build workflow (DRY principle)
- **Build Process**: Enhanced build workflow to trigger Docker builds on main branch pushes
- Changed app to run in the browser using reflex

## [2.4.1] - 2025-07-07

Expand Down
14 changes: 14 additions & 0 deletions Caddyfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
http://:8080 # has to match the frontend port (exposed port by docker)

encode gzip

@backend_routes path /_event/* /ping /_upload /_upload/*
handle @backend_routes {
reverse_proxy localhost:8000 # has to match the backend port of the reflex server
}

root * /srv
route {
try_files {path} {path} /404.html
file_server
}
Loading
Loading