v4.1.22: Исправлено удаление записей (HOTFIX)
- FIX: DELETE /api/records/:id возвращал 500 (no such column: deleted_by)
- Было: SET deleted_at = CURRENT_TIMESTAMP, deleted_by = ?
- Стало: SET deleted_at = CURRENT_TIMESTAMP, deleted = 1
- Добавлены логи: console.log('[DELETE]...')
- Docker: убран docker-compose.yml из v4.1.21
- Добавлен fix-docker.sh и docker-compose-simple.yml
- Cache version: app.js?v=4.1.22
This commit is contained in:
18
Dockerfile
18
Dockerfile
@@ -15,16 +15,26 @@ FROM node:20-bookworm-slim
|
|||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
ENV NODE_ENV=production \
|
ENV NODE_ENV=production \
|
||||||
WRANGLER_SEND_METRICS=false
|
WRANGLER_SEND_METRICS=false \
|
||||||
|
PORT=3000 \
|
||||||
|
D1_BINDING=aknaproff-db \
|
||||||
|
PERSIST_PATH=/data
|
||||||
|
|
||||||
# Copy everything from builder (includes node_modules, dist, migrations, etc.)
|
# Copy everything from builder (includes node_modules, dist, migrations, etc.)
|
||||||
COPY --from=builder /app /app
|
COPY --from=builder /app /app
|
||||||
|
|
||||||
RUN chmod +x /app/docker-entrypoint.sh
|
# Create data directory
|
||||||
|
RUN mkdir -p /data
|
||||||
|
|
||||||
EXPOSE 3000
|
EXPOSE 3000
|
||||||
|
|
||||||
# Persist D1 SQLite data and seed marker between restarts
|
# Persist D1 SQLite data between restarts
|
||||||
VOLUME ["/data"]
|
VOLUME ["/data"]
|
||||||
|
|
||||||
ENTRYPOINT ["/app/docker-entrypoint.sh"]
|
# Start wrangler directly (no bash script needed)
|
||||||
|
CMD ["npx", "wrangler", "pages", "dev", "dist", \
|
||||||
|
"--local", \
|
||||||
|
"--d1=aknaproff-db", \
|
||||||
|
"--persist-to=/data", \
|
||||||
|
"--ip=0.0.0.0", \
|
||||||
|
"--port=3000"]
|
||||||
|
|||||||
124
HOTFIX_v4.1.22.md
Normal file
124
HOTFIX_v4.1.22.md
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
# 🔧 HOTFIX v4.1.22 - ИСПРАВЛЕНО УДАЛЕНИЕ ЗАПИСЕЙ
|
||||||
|
|
||||||
|
**Дата**: 2026-01-14
|
||||||
|
**Версия**: v4.1.22 FINAL
|
||||||
|
**Приоритет**: HIGH (Критическая функция не работала)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📋 **ПРОБЛЕМА**
|
||||||
|
|
||||||
|
### **Не работало удаление записей**
|
||||||
|
|
||||||
|
**Симптомы:**
|
||||||
|
- Ошибка 500 при попытке удалить запись
|
||||||
|
- Консоль: `DELETE /api/records/50 [HTTP/1.1 500 Internal Server Error]`
|
||||||
|
- Ошибка в логах: `D1_ERROR: no such column: deleted_by: SQLITE_ERROR`
|
||||||
|
|
||||||
|
**Причина:**
|
||||||
|
- Backend пытался записать в несуществующую колонку `deleted_by`
|
||||||
|
- Таблица `production_records` имеет только `deleted_at`, но не `deleted_by`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ✅ **ИСПРАВЛЕНИЕ**
|
||||||
|
|
||||||
|
**Файл:** `src/index.tsx`, endpoint `DELETE /api/records/:id`
|
||||||
|
|
||||||
|
**Было:**
|
||||||
|
```typescript
|
||||||
|
await c.env.DB.prepare(`
|
||||||
|
UPDATE production_records
|
||||||
|
SET deleted_at = CURRENT_TIMESTAMP, deleted_by = ?
|
||||||
|
WHERE id = ?
|
||||||
|
`).bind(userId || null, id).run()
|
||||||
|
```
|
||||||
|
|
||||||
|
**Стало:**
|
||||||
|
```typescript
|
||||||
|
await c.env.DB.prepare(`
|
||||||
|
UPDATE production_records
|
||||||
|
SET deleted_at = CURRENT_TIMESTAMP, deleted = 1
|
||||||
|
WHERE id = ?
|
||||||
|
`).bind(id).run()
|
||||||
|
```
|
||||||
|
|
||||||
|
**Изменения:**
|
||||||
|
- ✅ Убрана попытка записи в `deleted_by` (колонки нет в БД)
|
||||||
|
- ✅ Добавлена установка флага `deleted = 1`
|
||||||
|
- ✅ Добавлено логирование для отладки
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🧪 **ТЕСТИРОВАНИЕ**
|
||||||
|
|
||||||
|
### **Test: Удаление записи ✅**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Было записей: 6
|
||||||
|
DELETE /api/records/49
|
||||||
|
# Результат: {"success":true}
|
||||||
|
# Стало записей: 5
|
||||||
|
```
|
||||||
|
|
||||||
|
**Проверка в БД:**
|
||||||
|
```sql
|
||||||
|
SELECT id, deleted, deleted_at FROM production_records WHERE id = 49;
|
||||||
|
-- Результат: 49|1|2026-01-14 20:30:54
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📦 **ФАЙЛЫ**
|
||||||
|
|
||||||
|
**Изменённые файлы:**
|
||||||
|
- `src/index.tsx` - endpoint `DELETE /api/records/:id`
|
||||||
|
|
||||||
|
**Версия:**
|
||||||
|
- `public/original.html` - обновлена до v4.1.22
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🚀 **РАЗВЁРТЫВАНИЕ**
|
||||||
|
|
||||||
|
### **ARM Synology:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Остановить контейнер
|
||||||
|
sudo docker-compose down
|
||||||
|
|
||||||
|
# 2. Распаковать новый архив
|
||||||
|
unzip aknaproff_production_v4.1.22_ARM_FINAL.zip
|
||||||
|
|
||||||
|
# 3. Запустить с пересборкой
|
||||||
|
cd backend
|
||||||
|
sudo docker-compose up -d --build
|
||||||
|
|
||||||
|
# 4. Проверить что удаление работает
|
||||||
|
# Войти как admin → удалить запись → проверить что удалилась
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ✅ **РЕЗУЛЬТАТ**
|
||||||
|
|
||||||
|
- ✅ Удаление записей работает корректно
|
||||||
|
- ✅ Записи помечаются как удалённые (soft delete)
|
||||||
|
- ✅ `deleted = 1` и `deleted_at` устанавливаются правильно
|
||||||
|
- ✅ Ошибка "no such column: deleted_by" исправлена
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📊 **ИСТОРИЯ ВЕРСИЙ**
|
||||||
|
|
||||||
|
| Версия | Изменения |
|
||||||
|
|--------|-----------|
|
||||||
|
| v4.1.20 | Исправлена форма настроек (смена пароля) |
|
||||||
|
| v4.1.21 | Убрано ограничение длины поля "Värv" |
|
||||||
|
| **v4.1.22** | **Исправлено удаление записей** |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Статус**: ✅ ГОТОВО
|
||||||
|
**Тестирование**: ✅ ПРОЙДЕНО
|
||||||
|
**Развёртывание**: ГОТОВО К ИСПОЛЬЗОВАНИЮ
|
||||||
8
dist/_worker.js
vendored
8
dist/_worker.js
vendored
File diff suppressed because one or more lines are too long
2
dist/original.html
vendored
2
dist/original.html
vendored
@@ -1225,7 +1225,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/axios@1.6.0/dist/axios.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/axios@1.6.0/dist/axios.min.js"></script>
|
||||||
<script src="/static/app.js?v=4.1.21"></script>
|
<script src="/static/app.js?v=4.1.22"></script>
|
||||||
|
|
||||||
|
|
||||||
</body></html>
|
</body></html>
|
||||||
20
docker-compose-simple.yml
Normal file
20
docker-compose-simple.yml
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
version: "3.8"
|
||||||
|
|
||||||
|
services:
|
||||||
|
aknaproff-backend:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
container_name: aknaproff-backend
|
||||||
|
ports:
|
||||||
|
- "8180:3000"
|
||||||
|
environment:
|
||||||
|
PORT: 3000
|
||||||
|
D1_BINDING: aknaproff-db
|
||||||
|
PERSIST_PATH: /data
|
||||||
|
SEED_DATA: "false"
|
||||||
|
SKIP_MIGRATIONS: "true"
|
||||||
|
WRANGLER_SEND_METRICS: "false"
|
||||||
|
volumes:
|
||||||
|
- ./data:/data
|
||||||
|
restart: unless-stopped
|
||||||
@@ -5,24 +5,9 @@ services:
|
|||||||
build:
|
build:
|
||||||
context: .
|
context: .
|
||||||
dockerfile: Dockerfile
|
dockerfile: Dockerfile
|
||||||
platforms:
|
|
||||||
- linux/amd64
|
|
||||||
- linux/arm64/v8
|
|
||||||
platform: linux/arm64/v8 # Explicit for ARM Synology
|
|
||||||
container_name: aknaproff-backend
|
container_name: aknaproff-backend
|
||||||
ports:
|
ports:
|
||||||
- "8180:3000"
|
- "8180:3000"
|
||||||
environment:
|
|
||||||
PORT: 3000
|
|
||||||
D1_BINDING: aknaproff-db
|
|
||||||
PERSIST_PATH: /data
|
|
||||||
SEED_DATA: "false" # Set to "true" on first run to load seed.sql automatically
|
|
||||||
SKIP_MIGRATIONS: "true" # ⚠️ Skip migrations to use existing database
|
|
||||||
WRANGLER_SEND_METRICS: "false"
|
|
||||||
volumes:
|
volumes:
|
||||||
- ./data:/data
|
- ./data:/data
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
|
||||||
volumes:
|
|
||||||
d1-data:
|
|
||||||
driver: local
|
|
||||||
|
|||||||
69
fix-docker.sh
Executable file
69
fix-docker.sh
Executable file
@@ -0,0 +1,69 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Quick fix script for Synology Docker "lease not found" error
|
||||||
|
|
||||||
|
echo "=== AKNAPROFF Docker Fix Script ==="
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Stop and remove old containers
|
||||||
|
echo "1. Stopping and removing old containers..."
|
||||||
|
sudo docker-compose down -v 2>/dev/null || true
|
||||||
|
|
||||||
|
# Prune containers
|
||||||
|
echo "2. Cleaning orphan containers..."
|
||||||
|
sudo docker container prune -f
|
||||||
|
|
||||||
|
# Prune build cache
|
||||||
|
echo "3. Cleaning build cache..."
|
||||||
|
sudo docker builder prune -f
|
||||||
|
|
||||||
|
# Remove old image if exists
|
||||||
|
echo "4. Removing old image..."
|
||||||
|
sudo docker rmi tootmine-aknaproff-backend:latest 2>/dev/null || true
|
||||||
|
sudo docker rmi aknaproff-backend:latest 2>/dev/null || true
|
||||||
|
|
||||||
|
# System prune
|
||||||
|
echo "5. System cleanup..."
|
||||||
|
sudo docker system prune -f
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "=== Cleanup complete ==="
|
||||||
|
echo ""
|
||||||
|
echo "Choose build method:"
|
||||||
|
echo "1) Use docker-compose.yml (with platform support)"
|
||||||
|
echo "2) Use docker-compose-simple.yml (without platform - RECOMMENDED for Synology)"
|
||||||
|
echo ""
|
||||||
|
read -p "Enter choice [1 or 2]: " choice
|
||||||
|
|
||||||
|
case $choice in
|
||||||
|
1)
|
||||||
|
echo "Building with docker-compose.yml..."
|
||||||
|
sudo docker-compose build --no-cache
|
||||||
|
sudo docker-compose up -d --force-recreate
|
||||||
|
;;
|
||||||
|
2)
|
||||||
|
echo "Building with docker-compose-simple.yml..."
|
||||||
|
sudo docker-compose -f docker-compose-simple.yml build --no-cache
|
||||||
|
sudo docker-compose -f docker-compose-simple.yml up -d --force-recreate
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Invalid choice. Please run script again."
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "=== Checking status ==="
|
||||||
|
sudo docker ps | grep aknaproff
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "=== Checking logs (last 20 lines) ==="
|
||||||
|
sudo docker logs aknaproff-backend --tail=20
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "=== Testing API ==="
|
||||||
|
sleep 3
|
||||||
|
curl -s http://localhost:8180/api/years | head -20
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "=== Done ==="
|
||||||
|
echo "If container is running, access at: http://synology-ip:8180"
|
||||||
@@ -1225,7 +1225,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/axios@1.6.0/dist/axios.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/axios@1.6.0/dist/axios.min.js"></script>
|
||||||
<script src="/static/app.js?v=4.1.21"></script>
|
<script src="/static/app.js?v=4.1.22"></script>
|
||||||
|
|
||||||
|
|
||||||
</body></html>
|
</body></html>
|
||||||
@@ -291,11 +291,15 @@ app.delete('/api/records/:id', optionalAuthMiddleware, async (c) => {
|
|||||||
const id = c.req.param('id')
|
const id = c.req.param('id')
|
||||||
const userId = c.get('userId')
|
const userId = c.get('userId')
|
||||||
|
|
||||||
|
console.log('[DELETE] Deleting record:', id, 'by user:', userId)
|
||||||
|
|
||||||
await c.env.DB.prepare(`
|
await c.env.DB.prepare(`
|
||||||
UPDATE production_records
|
UPDATE production_records
|
||||||
SET deleted_at = CURRENT_TIMESTAMP, deleted_by = ?
|
SET deleted_at = CURRENT_TIMESTAMP, deleted = 1
|
||||||
WHERE id = ?
|
WHERE id = ?
|
||||||
`).bind(userId || null, id).run()
|
`).bind(id).run()
|
||||||
|
|
||||||
|
console.log('[DELETE] Record deleted successfully:', id)
|
||||||
|
|
||||||
return c.json({ success: true })
|
return c.json({ success: true })
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
Reference in New Issue
Block a user