fix: 404 при обновлении, стрелки периода, фильтры в URL, авто-категории и очистка истории
- Nginx: проксирование /api на backend (единая точка входа) - История: стрелки ← → для переключения недель/месяцев/годов - История: сохранение фильтров и пагинации в URL при F5 - Импорт: миграция 003 — дефолтные правила категорий (PYATEROCHK, AUCHAN и др.) - Настройки: вкладка «Данные» с кнопкой «Очистить историю» - Backend: DELETE /api/transactions для удаления всех транзакций - ClearHistoryModal: подтверждение чекбоксами и вводом «УДАЛИТЬ»
This commit is contained in:
@@ -1,20 +0,0 @@
|
||||
FROM node:20-alpine AS deps
|
||||
WORKDIR /app
|
||||
COPY package.json package-lock.json* ./
|
||||
RUN npm ci
|
||||
|
||||
FROM node:20-alpine AS build
|
||||
WORKDIR /app
|
||||
COPY --from=deps /app/node_modules ./node_modules
|
||||
COPY tsconfig*.json ./
|
||||
COPY src ./src
|
||||
RUN npm run build
|
||||
|
||||
FROM node:20-alpine AS runner
|
||||
WORKDIR /app
|
||||
ENV NODE_ENV=production
|
||||
COPY --from=deps /app/node_modules ./node_modules
|
||||
COPY --from=build /app/dist ./dist
|
||||
COPY package.json ./
|
||||
EXPOSE 3000
|
||||
CMD ["npm","run","start"]
|
||||
@@ -14,6 +14,7 @@ import categoryRulesRouter from './routes/categoryRules';
|
||||
import analyticsRouter from './routes/analytics';
|
||||
|
||||
const app = express();
|
||||
app.set('trust proxy', 1);
|
||||
|
||||
app.use(express.json({ limit: '10mb' }));
|
||||
app.use(cookieParser());
|
||||
|
||||
@@ -96,6 +96,43 @@ const migrations: { name: string; sql: string }[] = [
|
||||
ON CONFLICT DO NOTHING;
|
||||
`,
|
||||
},
|
||||
{
|
||||
name: '003_seed_category_rules',
|
||||
sql: `
|
||||
INSERT INTO category_rules (pattern, match_type, category_id, priority, requires_confirmation)
|
||||
SELECT pattern, match_type, category_id, priority, requires_confirmation
|
||||
FROM (VALUES
|
||||
('PYATEROCHK', 'contains', (SELECT id FROM categories WHERE name = 'Продукты' AND type = 'expense' LIMIT 1), 20, false),
|
||||
('ПЯТЕРОЧКА', 'contains', (SELECT id FROM categories WHERE name = 'Продукты' AND type = 'expense' LIMIT 1), 20, false),
|
||||
('AUCHAN', 'contains', (SELECT id FROM categories WHERE name = 'Продукты' AND type = 'expense' LIMIT 1), 20, false),
|
||||
('DOSTAVKA IZ PYATEROCHK', 'contains', (SELECT id FROM categories WHERE name = 'Продукты' AND type = 'expense' LIMIT 1), 25, false),
|
||||
('МЕТРО', 'contains', (SELECT id FROM categories WHERE name = 'Продукты' AND type = 'expense' LIMIT 1), 20, false),
|
||||
('PEREKRESTOK', 'contains', (SELECT id FROM categories WHERE name = 'Продукты' AND type = 'expense' LIMIT 1), 20, false),
|
||||
('MAGNIT', 'contains', (SELECT id FROM categories WHERE name = 'Продукты' AND type = 'expense' LIMIT 1), 20, false),
|
||||
('LENTA', 'contains', (SELECT id FROM categories WHERE name = 'Продукты' AND type = 'expense' LIMIT 1), 20, false),
|
||||
('SPORTMASTER', 'contains', (SELECT id FROM categories WHERE name = 'Спорт' AND type = 'expense' LIMIT 1), 20, false),
|
||||
('DECATHLON', 'contains', (SELECT id FROM categories WHERE name = 'Спорт' AND type = 'expense' LIMIT 1), 20, false),
|
||||
('PAYPARKING', 'contains', (SELECT id FROM categories WHERE name = 'Авто' AND type = 'expense' LIMIT 1), 20, false),
|
||||
('PARKING', 'contains', (SELECT id FROM categories WHERE name = 'Авто' AND type = 'expense' LIMIT 1), 15, false),
|
||||
('AZS', 'contains', (SELECT id FROM categories WHERE name = 'Авто' AND type = 'expense' LIMIT 1), 20, false),
|
||||
('ЯНДЕКС.ЕДА', 'contains', (SELECT id FROM categories WHERE name = 'Общепит' AND type = 'expense' LIMIT 1), 25, false),
|
||||
('MCDONALD', 'contains', (SELECT id FROM categories WHERE name = 'Общепит' AND type = 'expense' LIMIT 1), 20, false),
|
||||
('KFC', 'contains', (SELECT id FROM categories WHERE name = 'Общепит' AND type = 'expense' LIMIT 1), 20, false),
|
||||
('STARBUCKS', 'contains', (SELECT id FROM categories WHERE name = 'Общепит' AND type = 'expense' LIMIT 1), 20, false),
|
||||
('tapper.ru', 'contains', (SELECT id FROM categories WHERE name = 'Развлечения' AND type = 'expense' LIMIT 1), 20, false),
|
||||
('АПТЕКА', 'contains', (SELECT id FROM categories WHERE name = 'Здоровье' AND type = 'expense' LIMIT 1), 20, false),
|
||||
('РИГЛА', 'contains', (SELECT id FROM categories WHERE name = 'Здоровье' AND type = 'expense' LIMIT 1), 20, false),
|
||||
('OZON', 'contains', (SELECT id FROM categories WHERE name = 'Дом' AND type = 'expense' LIMIT 1), 15, false),
|
||||
('WILDBERRIES', 'contains', (SELECT id FROM categories WHERE name = 'Дом' AND type = 'expense' LIMIT 1), 15, false),
|
||||
('ЯНДЕКС ПЛЮС', 'contains', (SELECT id FROM categories WHERE name = 'Подписки' AND type = 'expense' LIMIT 1), 20, false),
|
||||
('SPOTIFY', 'contains', (SELECT id FROM categories WHERE name = 'Подписки' AND type = 'expense' LIMIT 1), 20, false),
|
||||
('НЕТФЛИКС', 'contains', (SELECT id FROM categories WHERE name = 'Подписки' AND type = 'expense' LIMIT 1), 20, false)
|
||||
) AS v(pattern, match_type, category_id, priority, requires_confirmation)
|
||||
WHERE category_id IS NOT NULL
|
||||
AND EXISTS (SELECT 1 FROM categories LIMIT 1)
|
||||
AND NOT EXISTS (SELECT 1 FROM category_rules LIMIT 1);
|
||||
`,
|
||||
},
|
||||
];
|
||||
|
||||
export async function runMigrations(): Promise<void> {
|
||||
|
||||
@@ -16,12 +16,13 @@ router.post(
|
||||
|
||||
const result = await authService.login({ login, password });
|
||||
if (!result) {
|
||||
res.status(401).json({ error: 'UNAUTHORIZED', message: 'Invalid credentials' });
|
||||
res.status(401).json({ error: 'UNAUTHORIZED', message: 'Неверный логин или пароль' });
|
||||
return;
|
||||
}
|
||||
|
||||
res.cookie('sid', result.sessionId, {
|
||||
httpOnly: true,
|
||||
secure: process.env.NODE_ENV === 'production',
|
||||
sameSite: 'lax',
|
||||
path: '/',
|
||||
});
|
||||
|
||||
@@ -4,6 +4,14 @@ import * as transactionService from '../services/transactions';
|
||||
|
||||
const router = Router();
|
||||
|
||||
router.delete(
|
||||
'/',
|
||||
asyncHandler(async (_req, res) => {
|
||||
const result = await transactionService.clearAllTransactions();
|
||||
res.json(result);
|
||||
}),
|
||||
);
|
||||
|
||||
router.get(
|
||||
'/',
|
||||
asyncHandler(async (req, res) => {
|
||||
|
||||
@@ -168,3 +168,8 @@ export async function updateTransaction(
|
||||
comment: r.comment ?? null,
|
||||
};
|
||||
}
|
||||
|
||||
export async function clearAllTransactions(): Promise<{ deleted: number }> {
|
||||
const result = await pool.query('DELETE FROM transactions RETURNING id');
|
||||
return { deleted: result.rowCount ?? 0 };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user