Implement environment variable setup for frontend; create .env.production and .env.development files if they don't exist, and update API URLs in frontend components to use dynamic API_BASE_URL for improved configuration management.

This commit is contained in:
Torsten Schulz (local)
2025-10-18 23:36:31 +02:00
parent b066ffdeb4
commit efbb699b4b
25 changed files with 577 additions and 41 deletions

210
DEPLOYMENT_CHECKLIST.md Normal file
View File

@@ -0,0 +1,210 @@
# TimeClock v3 - Deployment Checkliste
Diese Checkliste stellt sicher, dass alle Schritte für ein erfolgreiches Deployment durchgeführt wurden.
## Pre-Deployment (Lokal)
- [ ] **Git Status prüfen**: `git status` - Alle Änderungen committed?
- [ ] **Tests durchführen**: Lokal testen mit `npm run dev`
- [ ] **Dependencies aktuell**: `npm install` in backend/ und frontend/
- [ ] **Frontend-Build testen**: `cd frontend && npm run build`
- [ ] **Keine hardcodierten URLs**: `grep -r "localhost:3010" frontend/src/`
## Git Push
```bash
git add .
git commit -m "Deployment vX.X.X"
git push origin main
```
## Server-Vorbereitung
- [ ] **SSH-Zugang**: `ssh tsschulz@tsschulz.de`
- [ ] **Projekt-Verzeichnis**: `cd /var/www/timeclock`
- [ ] **Git Pull**: `git pull origin main`
## Deployment Durchführen
### Option 1: Automatisches Deployment (Empfohlen)
```bash
cd /var/www/timeclock
./deploy.sh update
```
Das Script führt automatisch aus:
- ✅ Datenbank-Backup
- ✅ Backend Dependencies installieren
- ✅ Frontend .env.production erstellen
- ✅ Frontend neu bauen (mit korrekter API-URL)
- ✅ Backend neu starten
- ✅ Apache neu laden
### Option 2: Manuelles Deployment
```bash
# 1. Backup
./deploy.sh backup
# 2. Backend
cd /var/www/timeclock/backend
npm install --production
pm2 restart timeclock-backend
# 3. Frontend
cd /var/www/timeclock/frontend
# .env.production prüfen/erstellen
cat > .env.production << 'EOF'
VITE_API_URL=/api
EOF
npm install
rm -rf dist/
npm run build
# 4. Apache neu laden
sudo systemctl reload apache2
```
## Post-Deployment Tests
- [ ] **Backend Health-Check**:
```bash
curl https://stechuhr3.tsschulz.de/api/health
# Erwartung: {"status":"ok","message":"TimeClock API v3.0.0",...}
```
- [ ] **Frontend lädt**:
```bash
curl -I https://stechuhr3.tsschulz.de
# Erwartung: HTTP/2 200
```
- [ ] **Keine localhost URLs**:
```bash
grep -r "localhost:3010" /var/www/timeclock/frontend/dist/
# Erwartung: Keine Treffer (oder nur Kommentare)
```
- [ ] **PM2 Status**:
```bash
pm2 status
# Erwartung: timeclock-backend | online
```
- [ ] **Browser-Tests**:
- [ ] https://stechuhr3.tsschulz.de lädt
- [ ] Login funktioniert
- [ ] API-Calls gehen an `/api` (nicht `localhost:3010`)
- [ ] Keine Console-Errors (F12)
## Rollback bei Problemen
```bash
cd /var/www/timeclock
./deploy.sh rollback
```
## Monitoring
```bash
# Backend-Logs live
pm2 logs timeclock-backend
# Apache-Logs
sudo tail -f /var/log/apache2/stechuhr3-error.log
# Backend-Status
./deploy.sh status
# Alle Logs
./deploy.sh logs
```
## Häufige Probleme
### Problem: Frontend zeigt "localhost:3010" Fehler
**Lösung:**
```bash
cd /var/www/timeclock/frontend
cat .env.production # Sollte VITE_API_URL=/api enthalten
rm -rf dist/
npm run build
```
### Problem: API 502 Bad Gateway
**Lösung:**
```bash
pm2 restart timeclock-backend
pm2 logs timeclock-backend --lines 50
```
### Problem: Apache zeigt Default-Page
**Lösung:**
```bash
sudo apache2ctl -S | grep stechuhr3 # Prüfe VirtualHost
sudo systemctl reload apache2
```
### Problem: Browser-Cache
**Lösung:**
- **Strg + Shift + R** (Hard Reload)
- Oder Private/Incognito Window
## Deployment-Frequenz
- **Bugfixes**: Sofort
- **Features**: Nach Testing auf Dev
- **Breaking Changes**: Mit Ankündigung
## Backup-Strategie
- **Automatisch**: Täglich um 2 Uhr (via Cronjob)
- **Manuell**: Vor jedem Deployment
- **Retention**: 30 Tage
- **Location**: `/var/backups/timeclock/`
## Secrets & Credentials
Niemals committen:
- ❌ `.env` Dateien
- ❌ SSL-Zertifikate
- ❌ Datenbank-Passwörter
- ❌ API-Keys
Verwende `.gitignore`:
```
.env
.env.local
.env.production.local
*.key
*.pem
```
## Deployment-Log
Dokumentiere jedes Deployment:
```
Datum: 2025-10-18
Version: v3.0.1
Änderungen:
- API-URLs von hardcoded auf .env umgestellt
- Apache2-Konfiguration korrigiert
- SSL-Zertifikat eingerichtet
Status: ✅ Erfolgreich
Probleme: Keine
```
---
**Viel Erfolg beim Deployment! 🚀**
Bei Fragen: Siehe `DEPLOYMENT.md`, `APACHE2_DEPLOYMENT.md` oder `CHECK_SERVICES.md`

View File

@@ -214,6 +214,39 @@ setup_frontend() {
print_info "Installiere Frontend-Dependencies..."
npm install
# .env.production erstellen falls nicht vorhanden
if [ ! -f ".env.production" ]; then
print_info "Erstelle .env.production..."
cat > .env.production << 'EOF'
# TimeClock v3 - Frontend Production Environment
# API Base URL (relativ, da Apache als Proxy dient)
VITE_API_URL=/api
EOF
print_success ".env.production erstellt"
else
print_success ".env.production bereits vorhanden"
fi
# .env.development erstellen falls nicht vorhanden (für lokale Entwicklung)
if [ ! -f ".env.development" ]; then
print_info "Erstelle .env.development..."
cat > .env.development << 'EOF'
# TimeClock v3 - Frontend Development Environment
VITE_API_URL=http://localhost:3010/api
EOF
print_success ".env.development erstellt"
fi
# Prüfe ob api.js existiert
if [ ! -f "src/config/api.js" ]; then
print_warning "src/config/api.js fehlt! Bitte git pull ausführen."
exit 1
fi
# Alte dist/ löschen für sauberen Build
print_info "Lösche alten Build..."
rm -rf dist/
print_info "Erstelle Produktions-Build..."
npm run build
@@ -222,6 +255,14 @@ setup_frontend() {
exit 1
fi
# Prüfe ob localhost:3010 noch im Build ist
if grep -r "localhost:3010" dist/ > /dev/null 2>&1; then
print_warning "⚠️ localhost:3010 noch im Build gefunden - eventuell falsches .env verwendet"
print_info "Prüfe .env.production: $(cat .env.production 2>/dev/null || echo 'nicht gefunden')"
else
print_success "✅ Build verwendet korrekte API-URL"
fi
print_success "Frontend Build erstellt!"
}
@@ -390,15 +431,40 @@ do_update() {
cd $BACKEND_DIR
npm install --production
# Frontend neu bauen
# Frontend aktualisieren
print_info "Aktualisiere Frontend..."
cd $FRONTEND_DIR
# .env.production prüfen/erstellen
if [ ! -f ".env.production" ]; then
print_info "Erstelle .env.production..."
cat > .env.production << 'EOF'
# TimeClock v3 - Frontend Production Environment
VITE_API_URL=/api
EOF
fi
npm install
# Sauberer Build
rm -rf dist/
npm run build
# Prüfe Build
if grep -r "localhost:3010" dist/ > /dev/null 2>&1; then
print_warning "⚠️ localhost:3010 noch im Build - bitte prüfen"
fi
# Service neu starten
restart_backend
# Apache Cache leeren
if [ "$WEBSERVER" = "apache2" ]; then
sudo systemctl reload apache2
else
sudo systemctl reload nginx
fi
print_success "Update abgeschlossen!"
}

View File

@@ -0,0 +1,2 @@
# TimeClock v3 - Frontend Development Environment
VITE_API_URL=http://localhost:3010/api

3
frontend/.env.production Normal file
View File

@@ -0,0 +1,3 @@
# TimeClock v3 - Frontend Production Environment
# API Base URL (relativ, da Apache als Proxy dient)
VITE_API_URL=/api

76
frontend/FIX_API_URLS.sh Executable file
View File

@@ -0,0 +1,76 @@
#!/bin/bash
# =============================================================================
# TimeClock v3 - Fix hardcodierte API URLs
# =============================================================================
set -e
echo "🔧 Ersetze hardcodierte API-URLs durch zentrale Konfiguration..."
cd "$(dirname "$0")"
# Backup erstellen
echo "📦 Erstelle Backup..."
tar -czf ~/timeclock-frontend-backup-$(date +%Y%m%d_%H%M%S).tar.gz src/
# .env.production erstellen
echo "📝 Erstelle .env.production..."
cat > .env.production << 'EOF'
# TimeClock v3 - Frontend Production Environment
# API Base URL (relativ, da Apache als Proxy dient)
VITE_API_URL=/api
EOF
# .env.development erstellen
echo "📝 Erstelle .env.development..."
cat > .env.development << 'EOF'
# TimeClock v3 - Frontend Development Environment
VITE_API_URL=http://localhost:3010/api
EOF
# Ersetze alle hardcodierten URLs in Views
echo "🔄 Ersetze URLs in Views..."
# Pattern: http://localhost:3010/api → ${API_URL}
# Muss in mehreren Schritten erfolgen, da die Syntax unterschiedlich ist
find src/views -name "*.vue" -type f -exec sed -i "s|'http://localhost:3010/api|'\${API_URL}|g" {} \;
find src/views -name "*.vue" -type f -exec sed -i 's|"http://localhost:3010/api|"${API_URL}|g' {} \;
find src/views -name "*.vue" -type f -exec sed -i 's|`http://localhost:3010/api|`${API_URL}|g' {} \;
# Füge API_URL Import hinzu wo benötigt
echo "📦 Füge Imports hinzu..."
# Einfacher Ansatz: Erstelle eine Liste der Dateien mit API-Aufrufen
grep -l "localhost:3010" src/views/*.vue 2>/dev/null | while read -r file; do
# Prüfe ob Import bereits vorhanden
if ! grep -q "import.*API_BASE_URL.*from.*@/config/api" "$file"; then
# Füge Import nach dem ersten <script setup> hinzu
sed -i '/<script setup>/a import { API_BASE_URL as API_URL } from '\''@/config/api'\''' "$file"
fi
done
find src/components -name "*.vue" -type f -exec sed -i "s|'http://localhost:3010/api|'\${API_URL}|g" {} \;
find src/components -name "*.vue" -type f -exec sed -i 's|"http://localhost:3010/api|"${API_URL}|g' {} \;
find src/components -name "*.vue" -type f -exec sed -i 's|`http://localhost:3010/api|`${API_URL}|g' {} \;
grep -l "localhost:3010" src/components/*.vue 2>/dev/null | while read -r file; do
if ! grep -q "import.*API_BASE_URL.*from.*@/config/api" "$file"; then
sed -i '/<script setup>/a import { API_BASE_URL as API_URL } from '\''@/config/api'\''' "$file"
fi
done
echo ""
echo "✅ Fertig!"
echo ""
echo "📋 Nächste Schritte:"
echo " 1. npm run build"
echo " 2. Kopiere dist/ auf den Server"
echo ""
echo "🔍 Prüfe ob alle URLs ersetzt wurden:"
echo " grep -r 'localhost:3010' src/"
echo ""
exit 0

View File

@@ -92,7 +92,7 @@ const fetchStats = async () => {
const fetchCurrentState = async () => {
try {
const response = await fetch('http://localhost:3010/api/time-entries/current-state', {
const response = await fetch('${API_URL}/time-entries/current-state', {
headers: authStore.getAuthHeaders()
})
@@ -108,7 +108,7 @@ const fetchCurrentState = async () => {
// Lade die aktuellen Worklog-Daten (nur einmal pro Minute)
const fetchWorklogData = async () => {
try {
const response = await fetch('http://localhost:3010/api/time-entries/running', {
const response = await fetch('${API_URL}/time-entries/running', {
headers: authStore.getAuthHeaders()
})
@@ -277,7 +277,7 @@ const handleAction = async (action) => {
try {
loading.value = true
const response = await fetch('http://localhost:3010/api/time-entries/clock', {
const response = await fetch('${API_URL}/time-entries/clock', {
method: 'POST',
headers: {
...authStore.getAuthHeaders(),

View File

@@ -0,0 +1,66 @@
// TimeClock v3 - API Configuration
// Zentrale Konfiguration für alle API-Anfragen
// API Base URL aus Umgebungsvariablen
// In Production: /api (relativ, durch Apache Proxy)
// In Development: http://localhost:3010/api
export const API_BASE_URL = import.meta.env.VITE_API_URL || '/api'
// Vollständige API URL (für absolute URLs falls benötigt)
export const getApiUrl = (endpoint) => {
// Wenn endpoint mit / beginnt, direkt verwenden
if (endpoint.startsWith('/')) {
return `${API_BASE_URL}${endpoint}`
}
// Sonst / hinzufügen
return `${API_BASE_URL}/${endpoint}`
}
// Hilfsfunktion für Auth-Header
export const getAuthHeaders = () => {
const token = localStorage.getItem('timeclock_token')
const headers = {
'Content-Type': 'application/json'
}
if (token) {
headers['Authorization'] = `Bearer ${token}`
}
return headers
}
// Hilfsfunktion für API-Requests mit Fehlerbehandlung
export const apiRequest = async (endpoint, options = {}) => {
const url = getApiUrl(endpoint)
const defaultOptions = {
headers: getAuthHeaders()
}
const response = await fetch(url, {
...defaultOptions,
...options,
headers: {
...defaultOptions.headers,
...options.headers
}
})
const data = await response.json()
if (!response.ok) {
throw new Error(data.error || data.message || 'API-Anfrage fehlgeschlagen')
}
return data
}
export default {
API_BASE_URL,
getApiUrl,
getAuthHeaders,
apiRequest
}

View File

@@ -1,7 +1,8 @@
import { defineStore } from 'pinia'
import { ref } from 'vue'
import { API_BASE_URL } from '@/config/api'
const API_URL = 'http://localhost:3010/api'
const API_URL = API_BASE_URL
export const useAuthStore = defineStore('auth', () => {
const user = ref(null)

View File

@@ -1,8 +1,9 @@
import { defineStore } from 'pinia'
import { ref } from 'vue'
import { useAuthStore } from './authStore'
import { API_BASE_URL } from '@/config/api'
const API_URL = 'http://localhost:3010/api'
const API_URL = API_BASE_URL
export const useTimeStore = defineStore('time', () => {
const entries = ref([])

View File

@@ -101,7 +101,7 @@ async function loadCalendar() {
error.value = null
const response = await fetch(
`http://localhost:3010/api/calendar?year=${selectedYear.value}&month=${selectedMonth.value}`,
`${API_URL}/calendar?year=${selectedYear.value}&month=${selectedMonth.value}`,
{
headers: {
'Authorization': `Bearer ${authStore.token}`

View File

@@ -137,7 +137,7 @@ async function exportData() {
// Download CSV/Excel-Datei
async function downloadFile(format) {
const url = `http://localhost:3010/api/export/${format}?startDate=${form.value.startDate}&endDate=${form.value.endDate}`
const url = `${API_URL}/export/${format}?startDate=${form.value.startDate}&endDate=${form.value.endDate}`
const response = await fetch(url, {
headers: {
@@ -166,7 +166,7 @@ async function downloadFile(format) {
// PDF-Export (Browser-Print)
async function exportPDF() {
const url = `http://localhost:3010/api/export/pdf?startDate=${form.value.startDate}&endDate=${form.value.endDate}`
const url = `${API_URL}/export/pdf?startDate=${form.value.startDate}&endDate=${form.value.endDate}`
const response = await fetch(url, {
headers: {

View File

@@ -212,7 +212,7 @@ const form = ref({
// Lade alle Bundesländer
async function loadStates() {
try {
const response = await fetch('http://localhost:3010/api/holidays/states', {
const response = await fetch('${API_URL}/holidays/states', {
headers: {
'Authorization': `Bearer ${authStore.token}`
}
@@ -233,7 +233,7 @@ async function loadStates() {
async function loadHolidays() {
try {
loading.value = true
const response = await fetch('http://localhost:3010/api/holidays', {
const response = await fetch('${API_URL}/holidays', {
headers: {
'Authorization': `Bearer ${authStore.token}`
}
@@ -276,7 +276,7 @@ async function createHoliday() {
try {
loading.value = true
const response = await fetch('http://localhost:3010/api/holidays', {
const response = await fetch('${API_URL}/holidays', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
@@ -318,7 +318,7 @@ async function deleteHoliday(id) {
try {
loading.value = true
const response = await fetch(`http://localhost:3010/api/holidays/${id}`, {
const response = await fetch(`${API_URL}/holidays/${id}`, {
method: 'DELETE',
headers: {
'Authorization': `Bearer ${authStore.token}`

View File

@@ -91,7 +91,7 @@ const form = ref({
// Lade alle Einladungen
async function loadInvitations() {
try {
const response = await fetch('http://localhost:3010/api/invite', {
const response = await fetch('${API_URL}/invite', {
headers: {
'Authorization': `Bearer ${authStore.token}`
}
@@ -116,7 +116,7 @@ async function sendInvite() {
try {
loading.value = true
const response = await fetch('http://localhost:3010/api/invite', {
const response = await fetch('${API_URL}/invite', {
method: 'POST',
headers: {
'Content-Type': 'application/json',

View File

@@ -145,7 +145,7 @@ const handleLogin = async () => {
const handleGoogleLogin = () => {
// Redirect zu Google OAuth
window.location.href = 'http://localhost:3010/api/auth/google'
window.location.href = '${API_URL}/auth/google'
}
</script>

View File

@@ -92,7 +92,7 @@ async function changePassword() {
try {
loading.value = true
const response = await fetch('http://localhost:3010/api/password', {
const response = await fetch('${API_URL}/password', {
method: 'PUT',
headers: {
'Content-Type': 'application/json',

View File

@@ -102,7 +102,7 @@ const form = ref({
// Lade alle Watcher
async function loadWatchers() {
try {
const response = await fetch('http://localhost:3010/api/watcher', {
const response = await fetch('${API_URL}/watcher', {
headers: {
'Authorization': `Bearer ${authStore.token}`
}
@@ -128,7 +128,7 @@ async function addWatcher() {
try {
loading.value = true
const response = await fetch('http://localhost:3010/api/watcher', {
const response = await fetch('${API_URL}/watcher', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
@@ -172,7 +172,7 @@ async function removeWatcher(id, email) {
try {
loading.value = true
const response = await fetch(`http://localhost:3010/api/watcher/${id}`, {
const response = await fetch(`${API_URL}/watcher/${id}`, {
method: 'DELETE',
headers: {
'Authorization': `Bearer ${authStore.token}`

View File

@@ -135,7 +135,7 @@ const form = ref({
async function loadProfile() {
try {
loading.value = true
const response = await fetch('http://localhost:3010/api/profile', {
const response = await fetch('${API_URL}/profile', {
headers: {
'Authorization': `Bearer ${authStore.token}`
}
@@ -165,7 +165,7 @@ async function loadProfile() {
// Lade Bundesländer
async function loadStates() {
try {
const response = await fetch('http://localhost:3010/api/profile/states', {
const response = await fetch('${API_URL}/profile/states', {
headers: {
'Authorization': `Bearer ${authStore.token}`
}
@@ -185,7 +185,7 @@ async function loadStates() {
async function saveProfile() {
try {
loading.value = true
const response = await fetch('http://localhost:3010/api/profile', {
const response = await fetch('${API_URL}/profile', {
method: 'PUT',
headers: {
'Content-Type': 'application/json',

View File

@@ -82,7 +82,7 @@ const { showModal, modalConfig, alert, confirm, onConfirm, onCancel } = useModal
async function loadUsers() {
try {
loading.value = true
const response = await fetch('http://localhost:3010/api/roles/users', {
const response = await fetch('${API_URL}/roles/users', {
headers: {
'Authorization': `Bearer ${authStore.token}`
}
@@ -133,7 +133,7 @@ async function demoteToUser(userId, userName) {
async function updateRole(userId, newRole) {
try {
loading.value = true
const response = await fetch(`http://localhost:3010/api/roles/users/${userId}`, {
const response = await fetch(`${API_URL}/roles/users/${userId}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',

View File

@@ -123,7 +123,7 @@ const form = ref({
async function loadSickEntries() {
try {
loading.value = true
const response = await fetch('http://localhost:3010/api/sick', {
const response = await fetch('${API_URL}/sick', {
headers: {
'Authorization': `Bearer ${authStore.token}`
}
@@ -145,7 +145,7 @@ async function loadSickEntries() {
// Lade alle Krankheitstypen
async function loadSickTypes() {
try {
const response = await fetch('http://localhost:3010/api/sick/types', {
const response = await fetch('${API_URL}/sick/types', {
headers: {
'Authorization': `Bearer ${authStore.token}`
}
@@ -171,7 +171,7 @@ async function createSick() {
try {
loading.value = true
const response = await fetch('http://localhost:3010/api/sick', {
const response = await fetch('${API_URL}/sick', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
@@ -212,7 +212,7 @@ async function deleteSick(id) {
try {
loading.value = true
const response = await fetch(`http://localhost:3010/api/sick/${id}`, {
const response = await fetch(`${API_URL}/sick/${id}`, {
method: 'DELETE',
headers: {
'Authorization': `Bearer ${authStore.token}`

View File

@@ -179,7 +179,7 @@ async function loadWorklogEntries() {
if (!date) return
// Hole alle Worklog-Einträge für das Datum vom Backend
const response = await fetch(`http://localhost:3010/api/timefix/worklog-entries?date=${date}`, {
const response = await fetch(`${API_URL}/timefix/worklog-entries?date=${date}`, {
headers: authStore.getAuthHeaders()
})
@@ -224,7 +224,7 @@ function onEntrySelected() {
async function loadTimefixes() {
try {
loading.value = true
const response = await fetch('http://localhost:3010/api/timefix', {
const response = await fetch('${API_URL}/timefix', {
headers: authStore.getAuthHeaders()
})
@@ -248,7 +248,7 @@ async function createTimefix() {
try {
loading.value = true
const response = await fetch('http://localhost:3010/api/timefix', {
const response = await fetch('${API_URL}/timefix', {
method: 'POST',
headers: {
...authStore.getAuthHeaders(),
@@ -290,7 +290,7 @@ async function deleteTimefix(id) {
try {
loading.value = true
const response = await fetch(`http://localhost:3010/api/timefix/${id}`, {
const response = await fetch(`${API_URL}/timefix/${id}`, {
method: 'DELETE',
headers: authStore.getAuthHeaders()
})

View File

@@ -177,7 +177,7 @@ function onWishtypeChange() {
async function loadTimewishes() {
try {
loading.value = true
const response = await fetch('http://localhost:3010/api/timewish', {
const response = await fetch('${API_URL}/timewish', {
headers: {
'Authorization': `Bearer ${authStore.token}`
}
@@ -205,7 +205,7 @@ async function createTimewish() {
try {
loading.value = true
const response = await fetch('http://localhost:3010/api/timewish', {
const response = await fetch('${API_URL}/timewish', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
@@ -248,7 +248,7 @@ async function deleteTimewish(id) {
try {
loading.value = true
const response = await fetch(`http://localhost:3010/api/timewish/${id}`, {
const response = await fetch(`${API_URL}/timewish/${id}`, {
method: 'DELETE',
headers: {
'Authorization': `Bearer ${authStore.token}`

View File

@@ -135,7 +135,7 @@ function onStartDateChange() {
async function loadVacations() {
try {
loading.value = true
const response = await fetch('http://localhost:3010/api/vacation', {
const response = await fetch('${API_URL}/vacation', {
headers: authStore.getAuthHeaders()
})
@@ -159,7 +159,7 @@ async function createVacation() {
try {
loading.value = true
const response = await fetch('http://localhost:3010/api/vacation', {
const response = await fetch('${API_URL}/vacation', {
method: 'POST',
headers: {
...authStore.getAuthHeaders(),
@@ -197,7 +197,7 @@ async function deleteVacation(id) {
try {
loading.value = true
const response = await fetch(`http://localhost:3010/api/vacation/${id}`, {
const response = await fetch(`${API_URL}/vacation/${id}`, {
method: 'DELETE',
headers: authStore.getAuthHeaders()
})

View File

@@ -233,7 +233,7 @@ const loadWeekData = async () => {
try {
loading.value = true
const response = await fetch(`http://localhost:3010/api/week-overview?weekOffset=${weekOffset.value}`, {
const response = await fetch(`${API_URL}/week-overview?weekOffset=${weekOffset.value}`, {
headers: {
'Authorization': `Bearer ${authStore.token}`,
'Content-Type': 'application/json'

View File

@@ -61,7 +61,7 @@ async function loadStatistics() {
loading.value = true
error.value = null
const response = await fetch(`http://localhost:3010/api/workdays?year=${selectedYear.value}`, {
const response = await fetch(`${API_URL}/workdays?year=${selectedYear.value}`, {
headers: {
'Authorization': `Bearer ${authStore.token}`
}

111
package-lock.json generated
View File

@@ -51,6 +51,7 @@
},
"devDependencies": {
"@vitejs/plugin-vue": "^4.5.2",
"terser": "^5.27.0",
"vite": "^5.0.8"
}
},
@@ -542,12 +543,55 @@
"integrity": "sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ==",
"license": "MIT"
},
"node_modules/@jridgewell/gen-mapping": {
"version": "0.3.13",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
"integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@jridgewell/sourcemap-codec": "^1.5.0",
"@jridgewell/trace-mapping": "^0.3.24"
}
},
"node_modules/@jridgewell/resolve-uri": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
"integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/@jridgewell/source-map": {
"version": "0.3.11",
"resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz",
"integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@jridgewell/gen-mapping": "^0.3.5",
"@jridgewell/trace-mapping": "^0.3.25"
}
},
"node_modules/@jridgewell/sourcemap-codec": {
"version": "1.5.5",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
"integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
"license": "MIT"
},
"node_modules/@jridgewell/trace-mapping": {
"version": "0.3.31",
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
"integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@jridgewell/resolve-uri": "^3.1.0",
"@jridgewell/sourcemap-codec": "^1.4.14"
}
},
"node_modules/@mapbox/node-pre-gyp": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz",
@@ -1052,6 +1096,19 @@
"node": ">= 0.6"
}
},
"node_modules/acorn": {
"version": "8.15.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
"dev": true,
"license": "MIT",
"bin": {
"acorn": "bin/acorn"
},
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/agent-base": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
@@ -1441,6 +1498,13 @@
"integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==",
"license": "BSD-3-Clause"
},
"node_modules/buffer-from": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
"dev": true,
"license": "MIT"
},
"node_modules/buffer-indexof-polyfill": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/buffer-indexof-polyfill/-/buffer-indexof-polyfill-1.0.2.tgz",
@@ -1616,6 +1680,13 @@
"color-support": "bin.js"
}
},
"node_modules/commander": {
"version": "2.20.3",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
"dev": true,
"license": "MIT"
},
"node_modules/compress-commons": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-4.1.2.tgz",
@@ -4207,6 +4278,16 @@
"node": ">=10"
}
},
"node_modules/source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
"dev": true,
"license": "BSD-3-Clause",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/source-map-js": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
@@ -4216,6 +4297,17 @@
"node": ">=0.10.0"
}
},
"node_modules/source-map-support": {
"version": "0.5.21",
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz",
"integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==",
"dev": true,
"license": "MIT",
"dependencies": {
"buffer-from": "^1.0.0",
"source-map": "^0.6.0"
}
},
"node_modules/spawn-command": {
"version": "0.0.2",
"resolved": "https://registry.npmjs.org/spawn-command/-/spawn-command-0.0.2.tgz",
@@ -4324,6 +4416,25 @@
"node": ">=6"
}
},
"node_modules/terser": {
"version": "5.44.0",
"resolved": "https://registry.npmjs.org/terser/-/terser-5.44.0.tgz",
"integrity": "sha512-nIVck8DK+GM/0Frwd+nIhZ84pR/BX7rmXMfYwyg+Sri5oGVE99/E3KvXqpC2xHFxyqXyGHTKBSioxxplrO4I4w==",
"dev": true,
"license": "BSD-2-Clause",
"dependencies": {
"@jridgewell/source-map": "^0.3.3",
"acorn": "^8.15.0",
"commander": "^2.20.0",
"source-map-support": "~0.5.20"
},
"bin": {
"terser": "bin/terser"
},
"engines": {
"node": ">=10"
}
},
"node_modules/timeclock-backend": {
"resolved": "backend",
"link": true