Compare commits
9 Commits
fa1a422747
...
v1.0.10
| Author | SHA1 | Date | |
|---|---|---|---|
| 9936cfd544 | |||
| b42c36ebdd | |||
| 395199f9b2 | |||
| a37b259f47 | |||
| 0606b71890 | |||
| b4fc4fc955 | |||
| 8774e85529 | |||
| 2b2f4f46c0 | |||
| c5306a15d1 |
@@ -20,6 +20,10 @@ on:
|
|||||||
description: "Run Playwright E2E gate"
|
description: "Run Playwright E2E gate"
|
||||||
required: true
|
required: true
|
||||||
default: "true"
|
default: "true"
|
||||||
|
skip_tests:
|
||||||
|
description: "Skip the entire tests stage"
|
||||||
|
required: true
|
||||||
|
default: "false"
|
||||||
deploy:
|
deploy:
|
||||||
description: "Run production deploy over SSH"
|
description: "Run production deploy over SSH"
|
||||||
required: true
|
required: true
|
||||||
@@ -31,7 +35,7 @@ on:
|
|||||||
require_manual_approval:
|
require_manual_approval:
|
||||||
description: "Require approval token for deploy job"
|
description: "Require approval token for deploy job"
|
||||||
required: true
|
required: true
|
||||||
default: "true"
|
default: "false"
|
||||||
approval_token:
|
approval_token:
|
||||||
description: "Approval token value when manual approval is required"
|
description: "Approval token value when manual approval is required"
|
||||||
required: false
|
required: false
|
||||||
@@ -96,31 +100,36 @@ jobs:
|
|||||||
timeout-minutes: 120
|
timeout-minutes: 120
|
||||||
needs: prepare
|
needs: prepare
|
||||||
steps:
|
steps:
|
||||||
|
- name: Skip tests stage
|
||||||
|
if: ${{ github.event.inputs.skip_tests == 'true' }}
|
||||||
|
run: echo "Skipping tests stage because skip_tests=true"
|
||||||
|
|
||||||
- name: Check out promoted ref
|
- name: Check out promoted ref
|
||||||
|
if: ${{ github.event.inputs.skip_tests != 'true' }}
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
ref: ${{ needs.prepare.outputs.target_ref }}
|
ref: ${{ needs.prepare.outputs.target_ref }}
|
||||||
|
|
||||||
- name: Set up Python for backend tests
|
- name: Set up Python for backend tests
|
||||||
if: ${{ github.event.inputs.run_backend_tests != 'false' || github.event.inputs.run_playwright_tests != 'false' }}
|
if: ${{ github.event.inputs.skip_tests != 'true' && (github.event.inputs.run_backend_tests != 'false' || github.event.inputs.run_playwright_tests != 'false') }}
|
||||||
uses: actions/setup-python@v4
|
uses: actions/setup-python@v4
|
||||||
with:
|
with:
|
||||||
python-version: "3.11"
|
python-version: "3.11"
|
||||||
|
|
||||||
- name: Install backend dependencies (runner python)
|
- name: Install backend dependencies (runner python)
|
||||||
if: ${{ github.event.inputs.run_backend_tests != 'false' }}
|
if: ${{ github.event.inputs.skip_tests != 'true' && github.event.inputs.run_backend_tests != 'false' }}
|
||||||
run: |
|
run: |
|
||||||
python -m pip install --upgrade pip
|
python -m pip install --upgrade pip
|
||||||
pip install -r backend/requirements.txt
|
pip install -r backend/requirements.txt
|
||||||
|
|
||||||
- name: Run backend unit tests
|
- name: Run backend unit tests
|
||||||
if: ${{ github.event.inputs.run_backend_tests != 'false' }}
|
if: ${{ github.event.inputs.skip_tests != 'true' && github.event.inputs.run_backend_tests != 'false' }}
|
||||||
run: |
|
run: |
|
||||||
cd backend
|
cd backend
|
||||||
pytest -q
|
pytest -q
|
||||||
|
|
||||||
- name: Set up Node.js
|
- name: Set up Node.js
|
||||||
if: ${{ github.event.inputs.run_frontend_tests != 'false' || github.event.inputs.run_playwright_tests != 'false' }}
|
if: ${{ github.event.inputs.skip_tests != 'true' && (github.event.inputs.run_frontend_tests != 'false' || github.event.inputs.run_playwright_tests != 'false') }}
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: "20.19.0"
|
node-version: "20.19.0"
|
||||||
@@ -128,33 +137,35 @@ jobs:
|
|||||||
cache-dependency-path: frontend/package-lock.json
|
cache-dependency-path: frontend/package-lock.json
|
||||||
|
|
||||||
- name: Install frontend dependencies
|
- name: Install frontend dependencies
|
||||||
if: ${{ github.event.inputs.run_frontend_tests != 'false' || github.event.inputs.run_playwright_tests != 'false' }}
|
if: ${{ github.event.inputs.skip_tests != 'true' && (github.event.inputs.run_frontend_tests != 'false' || github.event.inputs.run_playwright_tests != 'false') }}
|
||||||
run: npm ci
|
run: npm ci
|
||||||
working-directory: frontend
|
working-directory: frontend
|
||||||
|
|
||||||
- name: Run frontend unit tests
|
- name: Run frontend unit tests
|
||||||
if: ${{ github.event.inputs.run_frontend_tests != 'false' }}
|
if: ${{ github.event.inputs.skip_tests != 'true' && github.event.inputs.run_frontend_tests != 'false' }}
|
||||||
run: npm run test:unit --if-present
|
run: npm run test:unit --if-present
|
||||||
working-directory: frontend
|
working-directory: frontend
|
||||||
|
|
||||||
- name: Create backend venv for Playwright webServer
|
- name: Create backend venv for Playwright webServer
|
||||||
if: ${{ github.event.inputs.run_playwright_tests != 'false' }}
|
if: ${{ github.event.inputs.skip_tests != 'true' && github.event.inputs.run_playwright_tests != 'false' }}
|
||||||
run: |
|
run: |
|
||||||
python -m venv backend/.venv
|
python -m venv backend/.venv
|
||||||
backend/.venv/bin/python -m pip install --upgrade pip
|
backend/.venv/bin/python -m pip install --upgrade pip
|
||||||
backend/.venv/bin/python -m pip install -r backend/requirements.txt
|
backend/.venv/bin/python -m pip install -r backend/requirements.txt
|
||||||
|
|
||||||
- name: Install Playwright browsers
|
- name: Install Playwright browsers
|
||||||
if: ${{ github.event.inputs.run_playwright_tests != 'false' }}
|
if: ${{ github.event.inputs.skip_tests != 'true' && github.event.inputs.run_playwright_tests != 'false' }}
|
||||||
run: npx playwright install --with-deps
|
run: npx playwright install --with-deps
|
||||||
working-directory: frontend
|
working-directory: frontend
|
||||||
|
|
||||||
- name: Run Playwright tests
|
- name: Run Playwright tests
|
||||||
if: ${{ github.event.inputs.run_playwright_tests != 'false' }}
|
if: ${{ github.event.inputs.skip_tests != 'true' && github.event.inputs.run_playwright_tests != 'false' }}
|
||||||
run: npx playwright test --reporter=line
|
run: npx playwright test --reporter=line
|
||||||
working-directory: frontend
|
working-directory: frontend
|
||||||
env:
|
env:
|
||||||
CI: "true"
|
CI: "true"
|
||||||
|
PLAYWRIGHT_BASE_URL: "https://localhost:5173"
|
||||||
|
E2E_ACCESS_TOKEN_EXPIRY_MINUTES: "180"
|
||||||
|
|
||||||
deploy:
|
deploy:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
@@ -190,13 +201,27 @@ jobs:
|
|||||||
script: |
|
script: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
cd /opt
|
repo_dir="${{ secrets.DEPLOY_PROD_PATH }}"
|
||||||
if [ -d "chore/.git" ]; then
|
if [ -z "$repo_dir" ]; then
|
||||||
cd chore
|
repo_dir="$HOME/chore"
|
||||||
|
fi
|
||||||
|
|
||||||
|
repo_parent=$(dirname "$repo_dir")
|
||||||
|
if [ ! -d "$repo_parent" ]; then
|
||||||
|
mkdir -p "$repo_parent"
|
||||||
|
fi
|
||||||
|
if [ ! -w "$repo_parent" ]; then
|
||||||
|
echo "Deploy user $(whoami) cannot write to ${repo_parent}."
|
||||||
|
echo "Set DEPLOY_PROD_PATH to a writable location or grant write access before rerunning promotion."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -d "$repo_dir/.git" ]; then
|
||||||
|
cd "$repo_dir"
|
||||||
git fetch --all --tags
|
git fetch --all --tags
|
||||||
else
|
else
|
||||||
git clone --branch "${{ needs.prepare.outputs.target_ref }}" https://git.ryankegel.com/ryan/chore.git chore
|
git clone --branch "${{ needs.prepare.outputs.target_ref }}" https://git.ryankegel.com/ryan/chore.git "$repo_dir"
|
||||||
cd chore
|
cd "$repo_dir"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
git checkout "${{ needs.prepare.outputs.target_ref }}"
|
git checkout "${{ needs.prepare.outputs.target_ref }}"
|
||||||
@@ -236,17 +261,20 @@ jobs:
|
|||||||
PREV_DIGEST_TOKEN_SECRET=""
|
PREV_DIGEST_TOKEN_SECRET=""
|
||||||
PREV_VAPID_PUBLIC_KEY=""
|
PREV_VAPID_PUBLIC_KEY=""
|
||||||
PREV_VAPID_PRIVATE_KEY=""
|
PREV_VAPID_PRIVATE_KEY=""
|
||||||
|
PREV_REFRESH_TOKEN_EXPIRY_DAYS=""
|
||||||
if [ -f .env ]; then
|
if [ -f .env ]; then
|
||||||
PREV_SECRET_KEY=$(grep '^SECRET_KEY=' .env | head -n1 | cut -d '=' -f2- || true)
|
PREV_SECRET_KEY=$(grep '^SECRET_KEY=' .env | head -n1 | cut -d '=' -f2- || true)
|
||||||
PREV_DIGEST_TOKEN_SECRET=$(grep '^DIGEST_TOKEN_SECRET=' .env | head -n1 | cut -d '=' -f2- || true)
|
PREV_DIGEST_TOKEN_SECRET=$(grep '^DIGEST_TOKEN_SECRET=' .env | head -n1 | cut -d '=' -f2- || true)
|
||||||
PREV_VAPID_PUBLIC_KEY=$(grep '^VAPID_PUBLIC_KEY=' .env | head -n1 | cut -d '=' -f2- || true)
|
PREV_VAPID_PUBLIC_KEY=$(grep '^VAPID_PUBLIC_KEY=' .env | head -n1 | cut -d '=' -f2- || true)
|
||||||
PREV_VAPID_PRIVATE_KEY=$(grep '^VAPID_PRIVATE_KEY=' .env | head -n1 | cut -d '=' -f2- || true)
|
PREV_VAPID_PRIVATE_KEY=$(grep '^VAPID_PRIVATE_KEY=' .env | head -n1 | cut -d '=' -f2- || true)
|
||||||
|
PREV_REFRESH_TOKEN_EXPIRY_DAYS=$(grep '^REFRESH_TOKEN_EXPIRY_DAYS=' .env | head -n1 | cut -d '=' -f2- || true)
|
||||||
fi
|
fi
|
||||||
|
|
||||||
SECRET_KEY_VALUE="${{ secrets.PROD_SECRET_KEY }}"
|
SECRET_KEY_VALUE="${{ secrets.PROD_SECRET_KEY }}"
|
||||||
DIGEST_TOKEN_SECRET_VALUE="${{ secrets.PROD_DIGEST_TOKEN_SECRET }}"
|
DIGEST_TOKEN_SECRET_VALUE="${{ secrets.PROD_DIGEST_TOKEN_SECRET }}"
|
||||||
VAPID_PUBLIC_KEY_VALUE="${{ secrets.PROD_VAPID_PUBLIC_KEY }}"
|
VAPID_PUBLIC_KEY_VALUE="${{ secrets.PROD_VAPID_PUBLIC_KEY }}"
|
||||||
VAPID_PRIVATE_KEY_VALUE="${{ secrets.PROD_VAPID_PRIVATE_KEY }}"
|
VAPID_PRIVATE_KEY_VALUE="${{ secrets.PROD_VAPID_PRIVATE_KEY }}"
|
||||||
|
REFRESH_TOKEN_EXPIRY_DAYS_VALUE="${{ secrets.PROD_REFRESH_TOKEN_EXPIRY_DAYS }}"
|
||||||
|
|
||||||
if [ -z "$SECRET_KEY_VALUE" ]; then
|
if [ -z "$SECRET_KEY_VALUE" ]; then
|
||||||
if [ -n "$PREV_SECRET_KEY" ]; then
|
if [ -n "$PREV_SECRET_KEY" ]; then
|
||||||
@@ -276,10 +304,25 @@ jobs:
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if [ -z "$REFRESH_TOKEN_EXPIRY_DAYS_VALUE" ]; then
|
||||||
|
if [ -n "$PREV_REFRESH_TOKEN_EXPIRY_DAYS" ]; then
|
||||||
|
REFRESH_TOKEN_EXPIRY_DAYS_VALUE="$PREV_REFRESH_TOKEN_EXPIRY_DAYS"
|
||||||
|
else
|
||||||
|
REFRESH_TOKEN_EXPIRY_DAYS_VALUE="90"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! printf '%s' "$REFRESH_TOKEN_EXPIRY_DAYS_VALUE" | grep -Eq '^[0-9]+$'; then
|
||||||
|
echo "REFRESH_TOKEN_EXPIRY_DAYS must be a positive integer, got: $REFRESH_TOKEN_EXPIRY_DAYS_VALUE"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
cat > .env << EOF
|
cat > .env << EOF
|
||||||
FRONTEND_URL=${{ secrets.PROD_FRONTEND_URL }}
|
FRONTEND_URL=${{ secrets.PROD_FRONTEND_URL }}
|
||||||
|
BACKEND_HOST_PORT=${{ secrets.PROD_BACKEND_HOST_PORT }}
|
||||||
|
FRONTEND_HOST_PORT=${{ secrets.PROD_FRONTEND_HOST_PORT }}
|
||||||
SECRET_KEY=${SECRET_KEY_VALUE}
|
SECRET_KEY=${SECRET_KEY_VALUE}
|
||||||
REFRESH_TOKEN_EXPIRY_DAYS=${{ secrets.PROD_REFRESH_TOKEN_EXPIRY_DAYS }}
|
REFRESH_TOKEN_EXPIRY_DAYS=${REFRESH_TOKEN_EXPIRY_DAYS_VALUE}
|
||||||
DIGEST_TOKEN_SECRET=${DIGEST_TOKEN_SECRET_VALUE}
|
DIGEST_TOKEN_SECRET=${DIGEST_TOKEN_SECRET_VALUE}
|
||||||
VAPID_PUBLIC_KEY=${VAPID_PUBLIC_KEY_VALUE}
|
VAPID_PUBLIC_KEY=${VAPID_PUBLIC_KEY_VALUE}
|
||||||
VAPID_PRIVATE_KEY=${VAPID_PRIVATE_KEY_VALUE}
|
VAPID_PRIVATE_KEY=${VAPID_PRIVATE_KEY_VALUE}
|
||||||
@@ -291,7 +334,52 @@ jobs:
|
|||||||
|
|
||||||
chmod 600 .env .rollback-meta
|
chmod 600 .env .rollback-meta
|
||||||
|
|
||||||
|
backend_host_port=$(grep '^BACKEND_HOST_PORT=' .env | head -n1 | cut -d '=' -f2- || true)
|
||||||
|
frontend_host_port=$(grep '^FRONTEND_HOST_PORT=' .env | head -n1 | cut -d '=' -f2- || true)
|
||||||
|
if [ -z "$backend_host_port" ]; then
|
||||||
|
backend_host_port="5001"
|
||||||
|
fi
|
||||||
|
if [ -z "$frontend_host_port" ]; then
|
||||||
|
frontend_host_port="4601"
|
||||||
|
fi
|
||||||
|
|
||||||
docker-compose down
|
docker-compose down
|
||||||
|
|
||||||
|
wait_for_container_removal() {
|
||||||
|
container_name="$1"
|
||||||
|
attempts=0
|
||||||
|
while docker ps -a --format '{{.Names}}' | grep -qx "$container_name"; do
|
||||||
|
attempts=$((attempts + 1))
|
||||||
|
if [ "$attempts" -ge 12 ]; then
|
||||||
|
echo "Container ${container_name} still exists after waiting for teardown."
|
||||||
|
docker ps -a --filter "name=^${container_name}$"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
echo "Waiting for ${container_name} to be removed..."
|
||||||
|
sleep 5
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
wait_for_port_release() {
|
||||||
|
port="$1"
|
||||||
|
attempts=0
|
||||||
|
while docker ps -a --format '{{.ID}} {{.Names}} {{.Ports}} {{.Status}}' | grep -E "(^|[[:space:]])0\.0\.0\.0:${port}->|:::${port}->" >/dev/null 2>&1; do
|
||||||
|
attempts=$((attempts + 1))
|
||||||
|
if [ "$attempts" -ge 12 ]; then
|
||||||
|
echo "Port ${port} is still allocated after teardown. Blocking container(s):"
|
||||||
|
docker ps -a --format '{{.ID}} {{.Names}} {{.Ports}} {{.Status}}' | grep -E "(^|[[:space:]])0\.0\.0\.0:${port}->|:::${port}->" || true
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
echo "Waiting for port ${port} to be released..."
|
||||||
|
sleep 5
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
wait_for_container_removal chores-app-frontend-prod
|
||||||
|
wait_for_container_removal chores-app-backend-prod
|
||||||
|
wait_for_port_release "$frontend_host_port"
|
||||||
|
wait_for_port_release "$backend_host_port"
|
||||||
|
|
||||||
docker-compose pull
|
docker-compose pull
|
||||||
docker-compose up -d
|
docker-compose up -d
|
||||||
|
|
||||||
@@ -301,18 +389,22 @@ jobs:
|
|||||||
frontend_running=$(docker inspect -f '{{.State.Running}}' chores-app-frontend-prod 2>/dev/null || echo false)
|
frontend_running=$(docker inspect -f '{{.State.Running}}' chores-app-frontend-prod 2>/dev/null || echo false)
|
||||||
|
|
||||||
backend_ok=false
|
backend_ok=false
|
||||||
if curl -fsS http://localhost:5001/version >/dev/null 2>&1; then
|
if curl -fsS "http://localhost:${backend_host_port}/version" >/dev/null 2>&1; then
|
||||||
backend_ok=true
|
backend_ok=true
|
||||||
fi
|
fi
|
||||||
|
|
||||||
frontend_ok=false
|
frontend_ok=false
|
||||||
if curl -kfsS https://localhost/ >/dev/null 2>&1; then
|
if curl -kfsS "https://localhost:${frontend_host_port}/" >/dev/null 2>&1; then
|
||||||
frontend_ok=true
|
frontend_ok=true
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ "$backend_running" != "true" ] || [ "$frontend_running" != "true" ] || [ "$backend_ok" != "true" ] || [ "$frontend_ok" != "true" ]; then
|
if [ "$backend_running" != "true" ] || [ "$frontend_running" != "true" ] || [ "$backend_ok" != "true" ] || [ "$frontend_ok" != "true" ]; then
|
||||||
echo "Post-deploy health check failed."
|
echo "Post-deploy health check failed."
|
||||||
docker-compose ps
|
docker-compose ps
|
||||||
|
echo "--- Backend logs (last 200 lines) ---"
|
||||||
|
docker logs --tail 200 chores-app-backend-prod || true
|
||||||
|
echo "--- Frontend logs (last 120 lines) ---"
|
||||||
|
docker logs --tail 120 chores-app-frontend-prod || true
|
||||||
|
|
||||||
if [ "${{ github.event.inputs.rollback_on_failed_healthcheck }}" = "true" ]; then
|
if [ "${{ github.event.inputs.rollback_on_failed_healthcheck }}" = "true" ]; then
|
||||||
echo "Attempting rollback using predeploy-backup image tags..."
|
echo "Attempting rollback using predeploy-backup image tags..."
|
||||||
@@ -320,7 +412,7 @@ jobs:
|
|||||||
docker tag git.ryankegel.com:3000/kegel/chores/frontend:predeploy-backup git.ryankegel.com:3000/kegel/chores/frontend:latest || true
|
docker tag git.ryankegel.com:3000/kegel/chores/frontend:predeploy-backup git.ryankegel.com:3000/kegel/chores/frontend:latest || true
|
||||||
docker-compose up -d
|
docker-compose up -d
|
||||||
sleep 20
|
sleep 20
|
||||||
curl -fsS http://localhost:5001/version >/dev/null
|
curl -fsS "http://localhost:${backend_host_port}/version" >/dev/null
|
||||||
fi
|
fi
|
||||||
|
|
||||||
exit 1
|
exit 1
|
||||||
|
|||||||
@@ -39,6 +39,9 @@ UserQuery = Query()
|
|||||||
TokenQuery = Query()
|
TokenQuery = Query()
|
||||||
TOKEN_EXPIRY_MINUTES = 60 * 4
|
TOKEN_EXPIRY_MINUTES = 60 * 4
|
||||||
RESET_PASSWORD_TOKEN_EXPIRY_MINUTES = 10
|
RESET_PASSWORD_TOKEN_EXPIRY_MINUTES = 10
|
||||||
|
try:
|
||||||
|
ACCESS_TOKEN_EXPIRY_MINUTES = int(os.environ.get('ACCESS_TOKEN_EXPIRY_MINUTES', '15'))
|
||||||
|
except ValueError:
|
||||||
ACCESS_TOKEN_EXPIRY_MINUTES = 15
|
ACCESS_TOKEN_EXPIRY_MINUTES = 15
|
||||||
E2E_TEST_EMAIL = 'e2e@test.com'
|
E2E_TEST_EMAIL = 'e2e@test.com'
|
||||||
E2E_TEST_PASSWORD = 'E2eTestPass1!'
|
E2E_TEST_PASSWORD = 'E2eTestPass1!'
|
||||||
|
|||||||
@@ -6,10 +6,15 @@ services:
|
|||||||
image: git.ryankegel.com:3000/kegel/chores/backend:latest # Or specific version tag
|
image: git.ryankegel.com:3000/kegel/chores/backend:latest # Or specific version tag
|
||||||
container_name: chores-app-backend-prod # Added for easy identification
|
container_name: chores-app-backend-prod # Added for easy identification
|
||||||
ports:
|
ports:
|
||||||
- "5001:5000" # Host 5001 -> Container 5000
|
- "${BACKEND_HOST_PORT:-5001}:5000" # Host port -> Container 5000
|
||||||
environment:
|
environment:
|
||||||
- FLASK_ENV=production
|
- FLASK_ENV=production
|
||||||
- FRONTEND_URL=${FRONTEND_URL}
|
- FRONTEND_URL=${FRONTEND_URL}
|
||||||
|
- SECRET_KEY=${SECRET_KEY}
|
||||||
|
- REFRESH_TOKEN_EXPIRY_DAYS=${REFRESH_TOKEN_EXPIRY_DAYS}
|
||||||
|
- DIGEST_TOKEN_SECRET=${DIGEST_TOKEN_SECRET}
|
||||||
|
- VAPID_PUBLIC_KEY=${VAPID_PUBLIC_KEY}
|
||||||
|
- VAPID_PRIVATE_KEY=${VAPID_PRIVATE_KEY}
|
||||||
volumes:
|
volumes:
|
||||||
- chores-app-backend-data:/app/data # Assuming backend data storage; adjust path as needed
|
- chores-app-backend-data:/app/data # Assuming backend data storage; adjust path as needed
|
||||||
networks:
|
networks:
|
||||||
@@ -20,7 +25,7 @@ services:
|
|||||||
image: git.ryankegel.com:3000/kegel/chores/frontend:latest # Or specific version tag
|
image: git.ryankegel.com:3000/kegel/chores/frontend:latest # Or specific version tag
|
||||||
container_name: chores-app-frontend-prod # Added for easy identification
|
container_name: chores-app-frontend-prod # Added for easy identification
|
||||||
ports:
|
ports:
|
||||||
- "443:443" # Host 443 -> Container 443 (HTTPS)
|
- "${FRONTEND_HOST_PORT:-4601}:443" # Host port -> Container 443 (HTTPS)
|
||||||
environment:
|
environment:
|
||||||
- BACKEND_HOST=chores-app-backend # Points to internal backend service
|
- BACKEND_HOST=chores-app-backend # Points to internal backend service
|
||||||
depends_on:
|
depends_on:
|
||||||
|
|||||||
@@ -2,20 +2,20 @@
|
|||||||
"cookies": [
|
"cookies": [
|
||||||
{
|
{
|
||||||
"name": "refresh_token",
|
"name": "refresh_token",
|
||||||
"value": "oh4sQB_Q1Se4xydZHTIolU6FhvlKPIlDPsxfd_qe4wQ",
|
"value": "v-K0IrmQf50MVvYCdmQfnUor8Q8e9KURnPnyVPYRN8Q",
|
||||||
"domain": "localhost",
|
"domain": "localhost",
|
||||||
"path": "/api/auth",
|
"path": "/api/auth",
|
||||||
"expires": 1784867824.852037,
|
"expires": 1785003958.597918,
|
||||||
"httpOnly": true,
|
"httpOnly": true,
|
||||||
"secure": true,
|
"secure": true,
|
||||||
"sameSite": "Strict"
|
"sameSite": "Strict"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "access_token",
|
"name": "access_token",
|
||||||
"value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImUyZS1jY0B0ZXN0LmNvbSIsInVzZXJfaWQiOiI4NGY1Y2I3NS05NWU1LTRjMTItOTExOS02MzdkYjJlMWY1MGYiLCJ0b2tlbl92ZXJzaW9uIjowLCJleHAiOjE3NzcwOTI3MjR9.7-d4EvJEYtYSi_5ZFiU7tuxgc4ilVMBxhwzgAQZNTvY",
|
"value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImUyZS1jY0B0ZXN0LmNvbSIsInVzZXJfaWQiOiI0NDg5ZjZhNS1mZWEyLTQ0ZGEtYTczNy00MDQwNTQyMmU0YTEiLCJ0b2tlbl92ZXJzaW9uIjowLCJleHAiOjE3NzcyMjg4NTh9.V7gsUwZ-mjcRqCIwI2ppFVrGD16R18JJDQ5wsv_ul1k",
|
||||||
"domain": "localhost",
|
"domain": "localhost",
|
||||||
"path": "/",
|
"path": "/",
|
||||||
"expires": 1777092724.851439,
|
"expires": 1777228858.597862,
|
||||||
"httpOnly": true,
|
"httpOnly": true,
|
||||||
"secure": true,
|
"secure": true,
|
||||||
"sameSite": "Lax"
|
"sameSite": "Lax"
|
||||||
@@ -27,11 +27,11 @@
|
|||||||
"localStorage": [
|
"localStorage": [
|
||||||
{
|
{
|
||||||
"name": "authSyncEvent",
|
"name": "authSyncEvent",
|
||||||
"value": "{\"type\":\"logout\",\"at\":1777091824594}"
|
"value": "{\"type\":\"logout\",\"at\":1777227958453}"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "parentAuth",
|
"name": "parentAuth",
|
||||||
"value": "{\"expiresAt\":1777264625262}"
|
"value": "{\"expiresAt\":1777400758775}"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,20 +2,20 @@
|
|||||||
"cookies": [
|
"cookies": [
|
||||||
{
|
{
|
||||||
"name": "refresh_token",
|
"name": "refresh_token",
|
||||||
"value": "lIhurtvmo0oGcimN_8v59h9y3CrglEAloQO1QFw8RsQ",
|
"value": "s8cSiajWyuCcjv9zXjqsKrmEK8cRPp-VOWV74Cz_ueQ",
|
||||||
"domain": "localhost",
|
"domain": "localhost",
|
||||||
"path": "/api/auth",
|
"path": "/api/auth",
|
||||||
"expires": 1784867824.995155,
|
"expires": 1785003958.599099,
|
||||||
"httpOnly": true,
|
"httpOnly": true,
|
||||||
"secure": true,
|
"secure": true,
|
||||||
"sameSite": "Strict"
|
"sameSite": "Strict"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "access_token",
|
"name": "access_token",
|
||||||
"value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImUyZS1kZWxldGVAdGVzdC5jb20iLCJ1c2VyX2lkIjoiOWYzYTljNWMtM2IwMy00YTZlLTliNmUtYjE0MTQzZWNhMzNkIiwidG9rZW5fdmVyc2lvbiI6MCwiZXhwIjoxNzc3MDkyNzI0fQ.UB5MCIZ2PHbn3UyohNzmFbOCb3TR-KHxJPWB9qkWwCQ",
|
"value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImUyZS1kZWxldGVAdGVzdC5jb20iLCJ1c2VyX2lkIjoiYmE3MDNmMWItMzg0Zi00MzBjLWJiNjYtN2Q0YWRhMWIwYjRiIiwidG9rZW5fdmVyc2lvbiI6MCwiZXhwIjoxNzc3MjI4ODU4fQ.FnEEIVv2wchkrJhDiRuNiDBF-lIPkyZUU2UwLmLZVHM",
|
||||||
"domain": "localhost",
|
"domain": "localhost",
|
||||||
"path": "/",
|
"path": "/",
|
||||||
"expires": 1777092724.99406,
|
"expires": 1777228858.599061,
|
||||||
"httpOnly": true,
|
"httpOnly": true,
|
||||||
"secure": true,
|
"secure": true,
|
||||||
"sameSite": "Lax"
|
"sameSite": "Lax"
|
||||||
@@ -27,11 +27,11 @@
|
|||||||
"localStorage": [
|
"localStorage": [
|
||||||
{
|
{
|
||||||
"name": "authSyncEvent",
|
"name": "authSyncEvent",
|
||||||
"value": "{\"type\":\"logout\",\"at\":1777091824687}"
|
"value": "{\"type\":\"logout\",\"at\":1777227958453}"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "parentAuth",
|
"name": "parentAuth",
|
||||||
"value": "{\"expiresAt\":1777264625511}"
|
"value": "{\"expiresAt\":1777400758775}"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,20 +2,20 @@
|
|||||||
"cookies": [
|
"cookies": [
|
||||||
{
|
{
|
||||||
"name": "refresh_token",
|
"name": "refresh_token",
|
||||||
"value": "uMExObHukEdFnlszorcDq2XlbRW0AeIV71x_vSovEF4",
|
"value": "mqtPPFKJGGfFly1i0VW1MtJUvgIpb7LlWUEpFLn1hGc",
|
||||||
"domain": "localhost",
|
"domain": "localhost",
|
||||||
"path": "/api/auth",
|
"path": "/api/auth",
|
||||||
"expires": 1784867821.107956,
|
"expires": 1785022348.96348,
|
||||||
"httpOnly": true,
|
"httpOnly": true,
|
||||||
"secure": true,
|
"secure": true,
|
||||||
"sameSite": "Strict"
|
"sameSite": "Strict"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "access_token",
|
"name": "access_token",
|
||||||
"value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImUyZUB0ZXN0LmNvbSIsInVzZXJfaWQiOiI2ZWE5YWZmZC1jMGExLTRkMzUtOGJmYS1mZDBkOWY5NzFmOWQiLCJ0b2tlbl92ZXJzaW9uIjowLCJleHAiOjE3NzcwOTI3MjF9.rb64zKqS6uXqkgX_4kke37Rc6om68elt4Qa24Fy9NPo",
|
"value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImUyZUB0ZXN0LmNvbSIsInVzZXJfaWQiOiIzZDJmZmY0OS03MjNhLTRhMmUtYWRhOS1iMTE5N2VhYTI2MDkiLCJ0b2tlbl92ZXJzaW9uIjowLCJleHAiOjE3NzcyNTcxNDh9.g69Lwtay2i0jwEzAbYcRWM2oGoOm-4qpV8YC04X0v-0",
|
||||||
"domain": "localhost",
|
"domain": "localhost",
|
||||||
"path": "/",
|
"path": "/",
|
||||||
"expires": 1777092721.107403,
|
"expires": 1777257148.963437,
|
||||||
"httpOnly": true,
|
"httpOnly": true,
|
||||||
"secure": true,
|
"secure": true,
|
||||||
"sameSite": "Lax"
|
"sameSite": "Lax"
|
||||||
@@ -27,11 +27,11 @@
|
|||||||
"localStorage": [
|
"localStorage": [
|
||||||
{
|
{
|
||||||
"name": "authSyncEvent",
|
"name": "authSyncEvent",
|
||||||
"value": "{\"type\":\"logout\",\"at\":1777091820880}"
|
"value": "{\"type\":\"logout\",\"at\":1777246348801}"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "parentAuth",
|
"name": "parentAuth",
|
||||||
"value": "{\"expiresAt\":1777264621275}"
|
"value": "{\"expiresAt\":1777419149107}"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { test, expect, type APIRequestContext } from '@playwright/test'
|
|||||||
const CHILD_NAME = 'DigestApproveChoreChild'
|
const CHILD_NAME = 'DigestApproveChoreChild'
|
||||||
const CHORE_NAME = 'DigestApproveChoreChore'
|
const CHORE_NAME = 'DigestApproveChoreChore'
|
||||||
const CHORE_POINTS = 8
|
const CHORE_POINTS = 8
|
||||||
|
const DIGEST_ACTION_BASE_URL = process.env.PLAYWRIGHT_BASE_URL || 'https://localhost:5173'
|
||||||
|
|
||||||
async function createChild(request: APIRequestContext, name: string): Promise<string> {
|
async function createChild(request: APIRequestContext, name: string): Promise<string> {
|
||||||
const pre = await request.get('/api/child/list')
|
const pre = await request.get('/api/child/list')
|
||||||
@@ -81,7 +82,7 @@ test.describe('Digest Action Token — Approve Chore', () => {
|
|||||||
}) => {
|
}) => {
|
||||||
const unauthCtx = await playwright.request.newContext({
|
const unauthCtx = await playwright.request.newContext({
|
||||||
ignoreHTTPSErrors: true,
|
ignoreHTTPSErrors: true,
|
||||||
baseURL: 'https://localhost:5173',
|
baseURL: DIGEST_ACTION_BASE_URL,
|
||||||
})
|
})
|
||||||
const res = await unauthCtx.get(`/api/digest-action/${approveToken}`, {
|
const res = await unauthCtx.get(`/api/digest-action/${approveToken}`, {
|
||||||
maxRedirects: 0,
|
maxRedirects: 0,
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ const CHILD_NAME = 'DigestApproveRewardChild'
|
|||||||
const REWARD_NAME = 'DigestApproveRewardReward'
|
const REWARD_NAME = 'DigestApproveRewardReward'
|
||||||
const REWARD_COST = 15
|
const REWARD_COST = 15
|
||||||
const INITIAL_POINTS = 50
|
const INITIAL_POINTS = 50
|
||||||
|
const DIGEST_ACTION_BASE_URL = process.env.PLAYWRIGHT_BASE_URL || 'https://localhost:5173'
|
||||||
|
|
||||||
async function createChild(
|
async function createChild(
|
||||||
request: APIRequestContext,
|
request: APIRequestContext,
|
||||||
@@ -78,7 +79,7 @@ test.describe('Digest Action Token — Approve Reward', () => {
|
|||||||
}) => {
|
}) => {
|
||||||
const unauthCtx = await playwright.request.newContext({
|
const unauthCtx = await playwright.request.newContext({
|
||||||
ignoreHTTPSErrors: true,
|
ignoreHTTPSErrors: true,
|
||||||
baseURL: 'https://localhost:5173',
|
baseURL: DIGEST_ACTION_BASE_URL,
|
||||||
})
|
})
|
||||||
const res = await unauthCtx.get(`/api/digest-action/${approveToken}`, {
|
const res = await unauthCtx.get(`/api/digest-action/${approveToken}`, {
|
||||||
maxRedirects: 0,
|
maxRedirects: 0,
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { test, expect, type APIRequestContext } from '@playwright/test'
|
|||||||
const CHILD_NAME = 'DigestDenyChoreChild'
|
const CHILD_NAME = 'DigestDenyChoreChild'
|
||||||
const CHORE_NAME = 'DigestDenyChoreChore'
|
const CHORE_NAME = 'DigestDenyChoreChore'
|
||||||
const CHORE_POINTS = 8
|
const CHORE_POINTS = 8
|
||||||
|
const DIGEST_ACTION_BASE_URL = process.env.PLAYWRIGHT_BASE_URL || 'https://localhost:5173'
|
||||||
|
|
||||||
async function createChild(request: APIRequestContext, name: string): Promise<string> {
|
async function createChild(request: APIRequestContext, name: string): Promise<string> {
|
||||||
const pre = await request.get('/api/child/list')
|
const pre = await request.get('/api/child/list')
|
||||||
@@ -71,7 +72,7 @@ test.describe('Digest Action Token — Deny Chore', () => {
|
|||||||
}) => {
|
}) => {
|
||||||
const unauthCtx = await playwright.request.newContext({
|
const unauthCtx = await playwright.request.newContext({
|
||||||
ignoreHTTPSErrors: true,
|
ignoreHTTPSErrors: true,
|
||||||
baseURL: 'https://localhost:5173',
|
baseURL: DIGEST_ACTION_BASE_URL,
|
||||||
})
|
})
|
||||||
const res = await unauthCtx.get(`/api/digest-action/${denyToken}`, {
|
const res = await unauthCtx.get(`/api/digest-action/${denyToken}`, {
|
||||||
maxRedirects: 0,
|
maxRedirects: 0,
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ const CHILD_NAME = 'DigestDenyRewardChild'
|
|||||||
const REWARD_NAME = 'DigestDenyRewardReward'
|
const REWARD_NAME = 'DigestDenyRewardReward'
|
||||||
const REWARD_COST = 15
|
const REWARD_COST = 15
|
||||||
const INITIAL_POINTS = 50
|
const INITIAL_POINTS = 50
|
||||||
|
const DIGEST_ACTION_BASE_URL = process.env.PLAYWRIGHT_BASE_URL || 'https://localhost:5173'
|
||||||
|
|
||||||
async function createChild(
|
async function createChild(
|
||||||
request: APIRequestContext,
|
request: APIRequestContext,
|
||||||
@@ -78,7 +79,7 @@ test.describe('Digest Action Token — Deny Reward', () => {
|
|||||||
}) => {
|
}) => {
|
||||||
const unauthCtx = await playwright.request.newContext({
|
const unauthCtx = await playwright.request.newContext({
|
||||||
ignoreHTTPSErrors: true,
|
ignoreHTTPSErrors: true,
|
||||||
baseURL: 'https://localhost:5173',
|
baseURL: DIGEST_ACTION_BASE_URL,
|
||||||
})
|
})
|
||||||
const res = await unauthCtx.get(`/api/digest-action/${denyToken}`, {
|
const res = await unauthCtx.get(`/api/digest-action/${denyToken}`, {
|
||||||
maxRedirects: 0,
|
maxRedirects: 0,
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { test, expect, type APIRequestContext } from '@playwright/test'
|
|||||||
const CHILD_NAME = 'DigestErrorChild'
|
const CHILD_NAME = 'DigestErrorChild'
|
||||||
const CHORE_NAME = 'DigestErrorChore'
|
const CHORE_NAME = 'DigestErrorChore'
|
||||||
const CHORE_POINTS = 5
|
const CHORE_POINTS = 5
|
||||||
|
const DIGEST_ACTION_BASE_URL = process.env.PLAYWRIGHT_BASE_URL || 'https://localhost:5173'
|
||||||
|
|
||||||
async function createChild(request: APIRequestContext, name: string): Promise<string> {
|
async function createChild(request: APIRequestContext, name: string): Promise<string> {
|
||||||
const pre = await request.get('/api/child/list')
|
const pre = await request.get('/api/child/list')
|
||||||
@@ -33,7 +34,7 @@ async function createTask(
|
|||||||
async function getUnauthContext(playwright: any) {
|
async function getUnauthContext(playwright: any) {
|
||||||
return playwright.request.newContext({
|
return playwright.request.newContext({
|
||||||
ignoreHTTPSErrors: true,
|
ignoreHTTPSErrors: true,
|
||||||
baseURL: 'https://localhost:5173',
|
baseURL: DIGEST_ACTION_BASE_URL,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
14
frontend/package-lock.json
generated
14
frontend/package-lock.json
generated
@@ -16,6 +16,7 @@
|
|||||||
"@tsconfig/node22": "^22.0.2",
|
"@tsconfig/node22": "^22.0.2",
|
||||||
"@types/jsdom": "^27.0.0",
|
"@types/jsdom": "^27.0.0",
|
||||||
"@types/node": "^22.18.11",
|
"@types/node": "^22.18.11",
|
||||||
|
"@vitejs/plugin-basic-ssl": "^2.3.0",
|
||||||
"@vitejs/plugin-vue": "^6.0.1",
|
"@vitejs/plugin-vue": "^6.0.1",
|
||||||
"@vitest/eslint-plugin": "^1.3.23",
|
"@vitest/eslint-plugin": "^1.3.23",
|
||||||
"@vue/eslint-config-prettier": "^10.2.0",
|
"@vue/eslint-config-prettier": "^10.2.0",
|
||||||
@@ -2171,6 +2172,19 @@
|
|||||||
"url": "https://opencollective.com/eslint"
|
"url": "https://opencollective.com/eslint"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@vitejs/plugin-basic-ssl": {
|
||||||
|
"version": "2.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@vitejs/plugin-basic-ssl/-/plugin-basic-ssl-2.3.0.tgz",
|
||||||
|
"integrity": "sha512-bdyo8rB3NnQbikdMpHaML9Z1OZPBu6fFOBo+OtxsBlvMJtysWskmBcnbIDhUqgC8tcxNv/a+BcV5U+2nQMm1OQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": "^18.0.0 || ^20.0.0 || >=22.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"vite": "^6.0.0 || ^7.0.0 || ^8.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@vitejs/plugin-vue": {
|
"node_modules/@vitejs/plugin-vue": {
|
||||||
"version": "6.0.1",
|
"version": "6.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-6.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-6.0.1.tgz",
|
||||||
|
|||||||
@@ -26,6 +26,7 @@
|
|||||||
"@tsconfig/node22": "^22.0.2",
|
"@tsconfig/node22": "^22.0.2",
|
||||||
"@types/jsdom": "^27.0.0",
|
"@types/jsdom": "^27.0.0",
|
||||||
"@types/node": "^22.18.11",
|
"@types/node": "^22.18.11",
|
||||||
|
"@vitejs/plugin-basic-ssl": "^2.3.0",
|
||||||
"@vitejs/plugin-vue": "^6.0.1",
|
"@vitejs/plugin-vue": "^6.0.1",
|
||||||
"@vitest/eslint-plugin": "^1.3.23",
|
"@vitest/eslint-plugin": "^1.3.23",
|
||||||
"@vue/eslint-config-prettier": "^10.2.0",
|
"@vue/eslint-config-prettier": "^10.2.0",
|
||||||
|
|||||||
@@ -17,6 +17,8 @@ import {
|
|||||||
/**
|
/**
|
||||||
* See https://playwright.dev/docs/test-configuration.
|
* See https://playwright.dev/docs/test-configuration.
|
||||||
*/
|
*/
|
||||||
|
const frontendBaseUrl = process.env.PLAYWRIGHT_BASE_URL || 'https://localhost:5173'
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
testDir: './e2e',
|
testDir: './e2e',
|
||||||
/* Run tests in files in parallel */
|
/* Run tests in files in parallel */
|
||||||
@@ -32,7 +34,7 @@ export default defineConfig({
|
|||||||
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
|
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
|
||||||
use: {
|
use: {
|
||||||
/* Base URL to use in actions like `await page.goto('')`. */
|
/* Base URL to use in actions like `await page.goto('')`. */
|
||||||
baseURL: 'https://localhost:5173',
|
baseURL: frontendBaseUrl,
|
||||||
ignoreHTTPSErrors: true,
|
ignoreHTTPSErrors: true,
|
||||||
|
|
||||||
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
|
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
|
||||||
@@ -220,7 +222,7 @@ export default defineConfig({
|
|||||||
webServer: [
|
webServer: [
|
||||||
{
|
{
|
||||||
command: 'npm run dev',
|
command: 'npm run dev',
|
||||||
url: 'https://localhost:5173',
|
url: frontendBaseUrl,
|
||||||
reuseExistingServer: !process.env.CI,
|
reuseExistingServer: !process.env.CI,
|
||||||
stdout: 'pipe',
|
stdout: 'pipe',
|
||||||
stderr: 'pipe',
|
stderr: 'pipe',
|
||||||
@@ -242,6 +244,7 @@ export default defineConfig({
|
|||||||
DB_ENV: 'e2e',
|
DB_ENV: 'e2e',
|
||||||
DATA_ENV: 'e2e',
|
DATA_ENV: 'e2e',
|
||||||
SECRET_KEY: 'dev-secret-key-change-in-production',
|
SECRET_KEY: 'dev-secret-key-change-in-production',
|
||||||
|
ACCESS_TOKEN_EXPIRY_MINUTES: process.env.E2E_ACCESS_TOKEN_EXPIRY_MINUTES || '180',
|
||||||
REFRESH_TOKEN_EXPIRY_DAYS: '90',
|
REFRESH_TOKEN_EXPIRY_DAYS: '90',
|
||||||
DIGEST_TOKEN_SECRET: 'dev-digest-token',
|
DIGEST_TOKEN_SECRET: 'dev-digest-token',
|
||||||
VAPID_PUBLIC_KEY:
|
VAPID_PUBLIC_KEY:
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
{
|
{
|
||||||
"status": "passed",
|
"status": "interrupted",
|
||||||
"failedTests": []
|
"failedTests": []
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import { fileURLToPath, URL } from 'node:url'
|
import { fileURLToPath, URL } from 'node:url'
|
||||||
import { defineConfig, loadEnv } from 'vite'
|
import { defineConfig, loadEnv } from 'vite'
|
||||||
import vue from '@vitejs/plugin-vue'
|
import vue from '@vitejs/plugin-vue'
|
||||||
|
import basicSsl from '@vitejs/plugin-basic-ssl'
|
||||||
import fs from 'fs'
|
import fs from 'fs'
|
||||||
|
|
||||||
export default defineConfig(({ mode }) => {
|
export default defineConfig(({ mode }) => {
|
||||||
@@ -13,10 +14,10 @@ export default defineConfig(({ mode }) => {
|
|||||||
: undefined
|
: undefined
|
||||||
|
|
||||||
return {
|
return {
|
||||||
plugins: [vue()],
|
plugins: [vue(), basicSsl()],
|
||||||
server: {
|
server: {
|
||||||
host: '0.0.0.0',
|
host: '0.0.0.0',
|
||||||
https: httpsConfig,
|
https: httpsConfig ?? true,
|
||||||
proxy: {
|
proxy: {
|
||||||
'/api': {
|
'/api': {
|
||||||
target: `http://${backendHost}:5000`,
|
target: `http://${backendHost}:5000`,
|
||||||
|
|||||||
Reference in New Issue
Block a user