Compare commits

...
13 changed files with 345 additions and 833 deletions
+3 -1
View File
@@ -44,7 +44,9 @@ jobs:
echo "Generated tag: ${TAG}"
- name: Set up Docker Buildx
run: docker buildx inspect --bootstrap
run: |
docker buildx create --name container-builder --driver docker-container --use --bootstrap || \
docker buildx use container-builder
- name: Log in to Docker Hub
uses: docker/login-action@v3
-99
View File
@@ -1,99 +0,0 @@
name: Pull Request CD-Deploy to Staging
on:
workflow_dispatch:
jobs:
terraform-plan-stg:
name: Plan changs to staging
if: ${{ github.event_name == 'workflow_dispatch' || github.event.workflow_run.conclusion == 'success' }}
runs-on: easynode-debian
outputs:
has_changes: ${{ steps.check-changes.outputs.has_changes }}
permissions:
contents: read
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup Terraform
uses: hashicorp/setup-terraform@v3
- name: Show Terraform provider versions
working-directory: terraform-stg
run: terraform version
- name: Setup kubectl
uses: azure/setup-kubectl@v4
- name: Setup Kubeconfig
run: |
mkdir -p ~/.kube
echo "${{ secrets.KUBECONFIG_DATA }}" | base64 -d > ~/.kube/config
chmod 600 ~/.kube/config
- name: Validate cluster access
run: |
kubectl cluster-info
kubectl get namespace tasknote-stg
- name: Determine deployment values
id: deploy-vars
run: |
backend_image="rmcampos/tasknote-api:candidate"
frontend_image="rmcampos/tasknote-app:candidate"
echo "backend_image=$backend_image" >> "$GITHUB_OUTPUT"
echo "frontend_image=$frontend_image" >> "$GITHUB_OUTPUT"
- name: Terraform Fmt -check -diff
working-directory: terraform-stg
run: terraform fmt -check -diff
- name: Terraform Init
working-directory: terraform-stg
env:
AWS_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }}
run: terraform init -input=false
- name: Terraform Validate
working-directory: terraform-stg
run: terraform validate
- name: Terraform Plan
id: check-changes
working-directory: terraform-stg
env:
AWS_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }}
run: |
timeout 3m terraform plan -input=false -out=tfplan \
-var="db_user=${{ secrets.DB_USER }}" \
-var="db_password=${{ secrets.DB_PASSWORD }}" \
-var="db_name=${{ secrets.DB_NAME }}" \
-var="security_key=${{ secrets.JWT_SECURITY_KEY }}" \
-var="mailgun_apikey=${{ secrets.MAILGUN_API_KEY }}" \
-var="backend_image=${{ steps.deploy-vars.outputs.backend_image }}" \
-var="frontend_image=${{ steps.deploy-vars.outputs.frontend_image }}" \
-var="deploy_version=${{ github.run_id }}"
terraform show -json tfplan > tfplan.json
if jq -e '.resource_changes | length == 0' tfplan.json >/dev/null; then
echo "has_changes=false" >> "$GITHUB_OUTPUT"
echo "No changes to apply."
exit 0
else
echo "Changes detected. Proceeding with apply"
echo "has_changes=true" >> "$GITHUB_OUTPUT"
fi
- name: Terraform Apply
working-directory: terraform-stg
if: steps.check-changes.outputs.has_changes == 'true'
env:
AWS_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }}
KUBE_CONFIG_PATH: ~/.kube/config
run: timeout 1m terraform apply tfplan
@@ -1,88 +0,0 @@
name: Main CI-Backend
on:
workflow_dispatch:
jobs:
build-and-push:
name: Build & Push
runs-on: graalvm-25
permissions:
contents: write
packages: write
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
token: ${{ secrets.REGISTRY_TOKEN }}
- name: Cache Maven dependencies
uses: actions/cache@v3
with:
path: ~/.m2/repository
key: ${{ runner.os }}-maven-${{ hashFiles('**/server/pom.xml') }}
restore-keys: |
${{ runner.os }}-maven-
- name: Increment version in pom.xml
id: version
working-directory: ./server
run: |
CURRENT_VERSION=$(./mvnw help:evaluate -Dexpression=project.version -q -DforceStdout)
echo "Current version: ${CURRENT_VERSION}"
NEW_VERSION=$((CURRENT_VERSION + 1))
echo "New version: ${NEW_VERSION}"
./mvnw versions:set -DnewVersion=${NEW_VERSION} -DgenerateBackupFiles=false -q
echo "version=${NEW_VERSION}" >> $GITHUB_OUTPUT
- name: Commit version bump
run: |
git config user.name "gitea-actions[bot]"
git config user.email "gitea-actions[bot]@noreply.lightroasted.vps-kinghost.net"
git add server/pom.xml
git commit -m "chore: bump api version to ${{ steps.version.outputs.version }} [skip ci]"
git push
- name: Log in to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Find PR number
id: find_pr
run: |
PR_NUMBER=$(curl -s \
-H "Authorization: token ${{ secrets.REGISTRY_TOKEN }}" \
"https://lightroasted.vps-kinghost.net/api/v1/repos/${{ github.repository }}/commits/${{ github.sha }}/pull" \
| jq -r 'if type == "object" and .number != null then .number | tostring else empty end')
if [ -z "$PR_NUMBER" ]; then
echo "No merged PR found for this commit. Falling back to 'candidate' tag."
PR_NUMBER="candidate"
else
PR_NUMBER="pr-${PR_NUMBER}"
fi
echo "tag=${PR_NUMBER}" >> $GITHUB_OUTPUT
- name: Promote Docker image
run: |
docker pull docker.io/rmcampos/tasknote-api:${{ steps.find_pr.outputs.tag }}
docker tag docker.io/rmcampos/tasknote-api:${{ steps.find_pr.outputs.tag }} docker.io/rmcampos/tasknote-api:latest
docker tag docker.io/rmcampos/tasknote-api:${{ steps.find_pr.outputs.tag }} docker.io/rmcampos/tasknote-api:${{ steps.version.outputs.version }}
docker push docker.io/rmcampos/tasknote-api:latest
docker push docker.io/rmcampos/tasknote-api:${{ steps.version.outputs.version }}
- name: Create and push Git tag
run: |
git config user.name "gitea-actions[bot]"
git config user.email "gitea-actions[bot]@noreply.lightroasted.vps-kinghost.net"
git tag -a api-v${{ steps.version.outputs.version }} -m "Release API v${{ steps.version.outputs.version }}"
git push origin api-v${{ steps.version.outputs.version }}
- name: Log pushed images
run: |
echo "Pushed images:"
echo " docker.io/rmcampos/tasknote-api:latest"
echo " docker.io/rmcampos/tasknote-api:${{ steps.version.outputs.version }}"
@@ -1,75 +0,0 @@
name: Main CI-Frontend
on:
workflow_dispatch:
jobs:
build-and-push:
name: Build & Push
runs-on: easynode-debian
permissions:
contents: write
packages: write
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Docker Buildx
run: docker buildx inspect --bootstrap
- name: Log in to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Find PR number
id: find_pr
run: |
PR_NUMBER=$(curl -s \
-H "Authorization: token ${{ secrets.REGISTRY_TOKEN }}" \
"https://lightroasted.vps-kinghost.net/api/v1/repos/${{ github.repository }}/commits/${{ github.sha }}/pull" \
| jq -r 'if type == "object" and .number != null then .number | tostring else empty end')
if [ -z "$PR_NUMBER" ]; then
echo "No merged PR found for this commit. Falling back to 'candidate' tag."
PR_NUMBER="candidate"
else
PR_NUMBER="pr-${PR_NUMBER}"
fi
echo "tag=${PR_NUMBER}" >> $GITHUB_OUTPUT
- name: Extract version from image
id: version
run: |
docker pull rmcampos/tasknote-app:${{ steps.find_pr.outputs.tag }}
VITE_BUILD=$(docker inspect rmcampos/tasknote-app:${{ steps.find_pr.outputs.tag }} \
--format '{{ range .Config.Env }}{{ println . }}{{ end }}' \
| grep '^VITE_BUILD=' | cut -d= -f2)
if [ -z "$VITE_BUILD" ]; then
echo "Could not extract VITE_BUILD from image" >&2
exit 1
fi
echo "tag=${VITE_BUILD}" >> $GITHUB_OUTPUT
echo "Extracted version: ${VITE_BUILD}"
- name: Promote Docker image
run: |
docker buildx imagetools create \
--tag rmcampos/tasknote-app:latest \
rmcampos/tasknote-app:${{ steps.find_pr.outputs.tag }}
- name: Create and push Git tag
run: |
git config user.name "gitea-actions[bot]"
git config user.email "gitea-actions[bot]@noreply.lightroasted.vps-kinghost.net"
git tag -a ${{ steps.version.outputs.tag }} -m "Release ${{ steps.version.outputs.tag }}"
git push origin ${{ steps.version.outputs.tag }}
- name: Log pushed images
run: |
echo "Pushed images:"
echo " rmcampos/tasknote-app:latest"
echo " rmcampos/tasknote-app:${{ steps.version.outputs.tag }}"
-95
View File
@@ -1,95 +0,0 @@
name: Pull Request CI-Backend
on:
workflow_dispatch:
jobs:
run-checks:
name: Checks
runs-on: graalvm-25
permissions:
contents: read
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Cache Maven dependencies
uses: actions/cache@v3
with:
path: ~/.m2/repository
key: ${{ runner.os }}-maven-${{ hashFiles('**/server/pom.xml') }}
restore-keys: |
${{ runner.os }}-maven-
- name: Run Check Style
working-directory: ./server
run: ./mvnw --no-transfer-progress checkstyle:check -Dcheckstyle.skip=false
- name: Run build
working-directory: ./server
run: ./mvnw --no-transfer-progress clean compile -DskipTests
- name: Run tests
working-directory: ./server
run: ./mvnw --no-transfer-progress clean verify -P tests --file pom.xml
build-and-push:
name: Build & Push
runs-on: graalvm-25
needs: ["run-checks"]
permissions:
contents: read
deployments: write
packages: write
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Cache Maven dependencies
uses: actions/cache@v3
with:
path: ~/.m2/repository
key: ${{ runner.os }}-maven-${{ hashFiles('**/server/pom.xml') }}
restore-keys: |
${{ runner.os }}-maven-
- name: Log in to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build Docker image with Spring Boot
working-directory: ./server
run: |
./mvnw -Pnative -DskipTests spring-boot:build-image \
-Dspring-boot.build-image.imageName=rmcampos/tasknote-api:latest \
-Dspring-boot.build-image.builder=paketobuildpacks/builder-jammy-tiny:0.0.505
- name: Tag and push Docker image
run: |
docker tag docker.io/rmcampos/tasknote-api:latest docker.io/rmcampos/tasknote-api:candidate
docker tag docker.io/rmcampos/tasknote-api:latest docker.io/rmcampos/tasknote-api:pr-${{ github.event.pull_request.number }}
docker push docker.io/rmcampos/tasknote-api:candidate
docker push docker.io/rmcampos/tasknote-api:pr-${{ github.event.pull_request.number }}
- name: Create Gitea deployment for staging
if: ${{ github.event_name == 'pull_request' }}
run: |
curl -s -X POST \
-H "Authorization: token ${{ secrets.REGISTRY_TOKEN }}" \
-H "Content-Type: application/json" \
"https://lightroasted.vps-kinghost.net/api/v1/repos/${{ github.repository }}/statuses/${{ github.event.pull_request.head.sha }}" \
-d "{\"context\": \"staging/deploy\", \"state\": \"success\", \"description\": \"PR #${{ github.event.pull_request.number }} staging ready\", \"target_url\": \"https://tasknote-stg.darkroasted.vps-kinghost.net\"}"
- name: Log pushed images
run: |
echo "Pushed images:"
echo " docker.io/rmcampos/tasknote-api:candidate"
echo " docker.io/rmcampos/tasknote-api:pr-${{ github.event.pull_request.number }}"
-120
View File
@@ -1,120 +0,0 @@
name: Pull Request CI-Frontend
on:
workflow_dispatch:
jobs:
run-checks:
name: Checks
runs-on: easynode-debian
permissions:
contents: read
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Cache npm dependencies
uses: actions/cache@v3
with:
path: ~/.npm
key: ${{ runner.os }}-npm-${{ hashFiles('**/client/package-lock.json') }}
restore-keys: |
${{ runner.os }}-npm-
- name: Debug cache env
run: |
echo "CACHE_URL=${ACTIONS_CACHE_URL}"
echo "RUNTIME_URL=${ACTIONS_RUNTIME_URL}"
curl -s -w "\nHTTP: %{http_code}\n" \
-H "Authorization: Bearer ${ACTIONS_RUNTIME_TOKEN}" \
"${ACTIONS_CACHE_URL}_apis/artifactcache/cache?keys=test&version=test"
- name: Install dependencies
run: npm install
working-directory: ./client
- name: Run lint
run: npm run lint
working-directory: ./client
- name: Run build
run: npm run build
working-directory: ./client
- name: Run tests
run: npm run test:no-watch
working-directory: ./client
build-and-push:
name: Build & Push
runs-on: easynode-debian
needs: ["run-checks"]
permissions:
contents: write
packages: write
deployments: write
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Docker Buildx
run: docker buildx inspect --bootstrap
- name: Log in to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Extract metadata for Docker
id: meta
uses: docker/metadata-action@v5
with:
images: rmcampos/tasknote-app
tags: |
type=raw,value=candidate
type=raw,value=pr-${{ github.event.pull_request.number }}
- name: Generate version tag
id: version
run: |
DATE=$(date +'%Y.%m.%d')
TAG="app-v${DATE}.${{ github.run_number }}"
echo "tag=${TAG}" >> $GITHUB_OUTPUT
echo "Generated tag: ${TAG}"
- name: Build and push Docker image
uses: docker/build-push-action@v5
with:
context: ./client
push: true
tags: |
${{ steps.meta.outputs.tags }}
rmcampos/tasknote-app:${{ steps.version.outputs.tag }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=registry,ref=rmcampos/tasknote-app:buildcache
cache-to: type=registry,ref=rmcampos/tasknote-app:buildcache,mode=max
build-args: |
VITE_BUILD=${{ steps.version.outputs.tag }}
- name: Create Gitea deployment for staging
if: ${{ github.event_name == 'pull_request' }}
run: |
curl -s -X POST \
-H "Authorization: token ${{ secrets.REGISTRY_TOKEN }}" \
-H "Content-Type: application/json" \
"https://lightroasted.vps-kinghost.net/api/v1/repos/${{ github.repository }}/statuses/${{ github.event.pull_request.head.sha }}" \
-d "{\"context\": \"staging/deploy\", \"state\": \"success\", \"description\": \"PR #${{ github.event.pull_request.number }} staging ready\", \"target_url\": \"https://tasknote-stg.darkroasted.vps-kinghost.net\"}"
- name: Log pushed images
run: |
echo "Pushed images:"
echo " rmcampos/tasknote-app:candidate"
echo " rmcampos/tasknote-app:pr-${{ github.event.pull_request.number }}"
echo " rmcampos/tasknote-app:${{ steps.version.outputs.tag }}"
+25 -2
View File
@@ -7,6 +7,29 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
## 2026-08-07
### Added
- Link to the build number to point to the changelog file. (build 201)
### Changed
- All deps to latest version in client for patch target. (build 201)
- All deps to latest version in client for minor target. (build 201)
- Development files for ngrok locally. (build 201)
### Fixed
- Buildx error in build phase in CI. (build 201)
### Removed
- Lingering files from previous CI/CD workflows. (build 201)
```bash
# Docker images
docker pull rmcampos/tasknote-app:app-v2026.08.07.201
```
---
## 2026-07-28
### Added
@@ -17,8 +40,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
```bash
# Docker images
docker pull rmcampos/tasknote-app:app-v2026.07.28.000
docker pull rmcampos/tasknote-api:api-v2026.07.28.000
docker pull rmcampos/tasknote-app:app-v2026.07.28.195
docker pull rmcampos/tasknote-api:api-v2026.07.28.194
```
## 2026-07-23
+271 -336
View File
File diff suppressed because it is too large Load Diff
+13 -13
View File
@@ -13,24 +13,24 @@
"private": true,
"dependencies": {
"@popperjs/core": "^2.11.8",
"@types/node": "^26.1.1",
"@types/react-dom": "^19.2.3",
"@vitejs/plugin-react": "^6.0.4",
"@types/node": "^26.1.2",
"@types/react-dom": "^19.2.4",
"@vitejs/plugin-react": "^6.0.5",
"bootstrap": "^5.3.8",
"dompurify": "^3.4.12",
"dompurify": "^3.4.13",
"i18next": "^26.3.6",
"react": "^19.2.8",
"react-bootstrap": "^2.10.10",
"react-bootstrap-icons": "^1.11.6",
"react-charts": "^3.0.0-beta.57",
"react-dom": "^19.2.8",
"react-i18next": "^17.0.10",
"react-i18next": "^17.0.11",
"react-markdown": "^10.1.0",
"react-router": "^8.3.0",
"react-router-bootstrap": "^0.26.3",
"remark-gfm": "^4.0.1",
"typescript": "^6.0.3",
"vite": "^8.1.5"
"vite": "^8.2.1"
},
"scripts": {
"start": "vite --host",
@@ -70,25 +70,25 @@
"@stylistic/eslint-plugin": "^5.10.0",
"@testing-library/jest-dom": "^6.10.0",
"@testing-library/react": "^16.3.2",
"@testing-library/user-event": "^14.6.1",
"@testing-library/user-event": "^14.6.3",
"@types/jest": "^30.0.0",
"@types/react": "^19.2.17",
"@types/react": "^19.2.18",
"@types/react-router-bootstrap": "^0.26.8",
"@vitest/coverage-v8": "^4.1.10",
"cypress": "^15.19.0",
"cypress": "^15.20.0",
"eslint": "^9.39.5",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-import-x": "^4.17.1",
"eslint-plugin-jsdoc": "^63.2.0",
"eslint-plugin-jsdoc": "^63.3.3",
"eslint-plugin-n": "^18.2.2",
"eslint-plugin-promise": "^7.3.0",
"eslint-plugin-react": "^7.37.5",
"globals": "^17.7.0",
"globals": "^17.9.0",
"jsdom": "^29.1.1",
"prettier": "^3.9.6",
"sass": "^1.101.3",
"sass": "^1.102.0",
"source-map-support": "^0.5.21",
"typescript-eslint": "^8.65.0",
"typescript-eslint": "^8.66.0",
"vitest": "^4.1.10"
}
}
+10 -1
View File
@@ -25,6 +25,7 @@ function Sidebar(props: React.PropsWithChildren<Props>): React.ReactNode {
const [lastSeen, setLastSeen] = useState('');
const { t } = useTranslation();
const build = `Build: ${env.VITE_BUILD}`;
const changeLogUrl = 'https://lightroasted.vps-kinghost.net/rmcampos/tasknote/src/branch/main/CHANGELOG.md';
// Note: when selected, change class to plus-jakarta-sans-thin and add background
@@ -119,7 +120,15 @@ function Sidebar(props: React.PropsWithChildren<Props>): React.ReactNode {
<small>{t('sidebar_last_seen', { time: lastSeen })}</small>
</div>
)}
<small data-testid="footer-text">{build}</small>
<a
data-testid="footer-text"
href={changeLogUrl}
target="_blank"
rel="noopener noreferrer"
className="footer-link"
>
<small>{build}</small>
</a>
</div>
</div>
+9
View File
@@ -96,3 +96,12 @@ a.active {
border-left: #4CD964 0.3rem solid;
background: #ced6da linear-gradient(270deg, rgba(53, 99, 233, 0.36) -416.06%, rgba(53, 99, 233, 0) 94.8%);
}
.footer-link {
color: inherit;
text-decoration: none;
}
.footer-link:hover {
text-decoration: underline;
}
+12 -1
View File
@@ -3,6 +3,16 @@ import react from '@vitejs/plugin-react';
import { fileURLToPath } from 'url';
import path from 'path';
const proxyConfig = process.env.NGROK
? {
'/api': {
target: `http://${process.env.BACKEND_HOST || 'localhost'}:8585`,
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, ''),
},
}
: undefined;
export default defineConfig(({ mode }: ConfigEnv) => {
const config: UserConfig = {
define: {},
@@ -33,7 +43,8 @@ export default defineConfig(({ mode }: ConfigEnv) => {
},
server: {
port: 5000,
...(process.env.NGROK ? { allowedHosts: ['.ngrok-free.dev'] } : {})
...(process.env.NGROK ? { allowedHosts: ['.ngrok-free.dev'] } : {}),
proxy: proxyConfig,
},
preview: {
port: 5000
+2 -2
View File
@@ -7,11 +7,12 @@ services:
user: ${UID:-1000}:${GID:-1000}
ports:
- "5000:5000"
entrypoint: sh -c "npm i --no-update-notifier && npm start"
entrypoint: sh -c "npm i --no-update-notifier && export NGROK=1 && npm start"
environment:
VITE_BACKEND_SERVER: "/api"
VITE_BUILD: nightly
NGROK: 1
BACKEND_HOST: tasknote-api
volumes:
- "./client:/app"
working_dir: /app
@@ -92,4 +93,3 @@ services:
networks:
tasknote:
external: true