feat: integrate Serilog and Telegram notifications
TaxBaik CI/CD / build-and-deploy (push) Successful in 51s

- Add Serilog for structured logging (Console + File)
- Implement TelegramNotificationService for admin alerts
- Log successful/failed login attempts with Telegram notifications
- Add application startup/shutdown logging
- Log important events to Telegram Chat ID: -5585148480
- Configuration: Telegram:BotToken and Telegram:ChatId in appsettings

Features:
- Automatic daily log rotation
- Structured logging with timestamps
- Environment-aware alerts
- Error and info level Telegram messages

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-06-28 16:19:38 +09:00
parent e797da6140
commit 2bde490e9e
15 changed files with 517 additions and 8 deletions
+50
View File
@@ -0,0 +1,50 @@
import { test } from '@playwright/test';
test('check dashboard metrics', async ({ page }) => {
const baseUrl = 'http://178.104.200.7/taxbaik';
// Login
await page.goto(`${baseUrl}/admin/login`);
await page.fill('input[placeholder="사용자명"]', 'test_admin');
await page.fill('input[placeholder="비밀번호"]', 'TestAdmin@123456');
await page.click('button[type="submit"]');
await page.waitForURL(/admin\/dashboard/);
// Wait for page load
await page.waitForSelector('.admin-page-hero', { timeout: 5000 });
await page.waitForTimeout(2000);
// Check for metric cards
const metrics = page.locator('.admin-metric-card');
const metricCount = await metrics.count();
console.log(`\nFound ${metricCount} metric cards`);
// Log each metric value
for (let i = 0; i < Math.min(metricCount, 4); i++) {
const metric = metrics.nth(i);
const text = await metric.textContent();
console.log(` Metric ${i + 1}: ${text?.trim().slice(0, 100)}`);
}
// Check for data in main content area
const contentText = await page.locator('body').textContent();
const has총문의 = contentText?.includes('총 문의') || false;
const has신규문의 = contentText?.includes('신규 문의') || false;
const has활성공지 = contentText?.includes('활성 공지') || false;
const has예정세무신고 = contentText?.includes('예정 세무신고') || false;
console.log(`\nDashboard content check:`);
console.log(` - 총 문의: ${has총문의}`);
console.log(` - 신규 문의: ${has신규문의}`);
console.log(` - 활성 공지: ${has활성공지}`);
console.log(` - 예정 세무신고: ${has예정세무신고}`);
// Try to get text content from specific areas
const dashContent = await page.locator('.admin-content').textContent();
if (dashContent) {
console.log(`\nDashboard content length: ${dashContent.length}`);
// Check if there are numbers
const numbers = dashContent.match(/\d+/g) || [];
console.log(`Numbers found: ${numbers.slice(0, 10).join(', ')}`);
}
});
+96
View File
@@ -0,0 +1,96 @@
import { test, expect } from '@playwright/test';
test('production: verify all admin pages load correctly', async ({ page }) => {
const baseUrl = 'http://178.104.200.7/taxbaik';
// Login
console.log('🔐 Logging in...');
await page.goto(`${baseUrl}/admin/login`);
await page.fill('input[placeholder="사용자명"]', 'test_admin');
await page.fill('input[placeholder="비밀번호"]', 'TestAdmin@123456');
await page.click('button[type="submit"]');
await page.waitForURL(/admin\/dashboard/);
console.log('✓ Login successful\n');
const pageHero = page.locator('.admin-page-hero').first();
const loadingOverlay = page.locator('#blazor-loading');
// List of all admin pages to test (using direct URLs)
const pages = [
{ name: '📊 Dashboard', url: `${baseUrl}/admin/dashboard`, hasData: false },
{ name: '👥 Clients', url: `${baseUrl}/admin/clients`, hasData: true },
{ name: '📅 Tax Filings', url: `${baseUrl}/admin/tax-filings`, hasData: true },
{ name: '📢 Announcements', url: `${baseUrl}/admin/announcements`, hasData: false },
{ name: '❓ FAQs', url: `${baseUrl}/admin/faqs`, hasData: true },
{ name: '📝 Blog', url: `${baseUrl}/admin/blog`, hasData: true },
{ name: '🎭 Season Simulator', url: `${baseUrl}/admin/season-simulator`, hasData: false },
{ name: '❔ Inquiries', url: `${baseUrl}/admin/inquiries`, hasData: true },
{ name: '⚙️ Settings', url: `${baseUrl}/admin/settings`, hasData: false },
];
for (const pageInfo of pages) {
console.log(`${'─'.repeat(60)}`);
console.log(`Testing: ${pageInfo.name}`);
console.log(`URL: ${pageInfo.url}`);
console.log(`${'─'.repeat(60)}`);
const startTime = Date.now();
try {
// Navigate to page
await page.goto(pageInfo.url);
// Wait for page hero or basic element
try {
await pageHero.waitFor({ state: 'visible', timeout: 3000 });
console.log(` ✓ Page hero visible`);
} catch {
// Some pages might not have page hero, that's OK
}
// Check if page loaded successfully by looking for content
const pageContent = page.locator('body').first();
await pageContent.waitFor({ state: 'visible', timeout: 5000 });
// Wait for data if expected
if (pageInfo.hasData) {
try {
// Try to find table rows
await page.waitForSelector('tbody tr', { timeout: 8000 });
const rowCount = await page.locator('tbody tr').count();
if (rowCount > 0) {
console.log(` ✓ Data loaded: ${rowCount} rows`);
} else {
console.log(` ⚠️ Table found but no rows`);
}
} catch {
console.log(` ️ No table data (may not have table)`);
}
}
// Verify overlay is hidden
const overlayShown = await loadingOverlay.evaluate((el: HTMLElement) =>
el.classList.contains('show')
).catch(() => false);
if (!overlayShown) {
console.log(` ✓ Loading overlay hidden`);
} else {
console.log(` ⚠️ Loading overlay still visible`);
}
const totalTime = Date.now() - startTime;
console.log(` ⏱️ Load time: ${totalTime}ms`);
console.log(` ✅ PAGE LOADED SUCCESSFULLY\n`);
} catch (error) {
const totalTime = Date.now() - startTime;
console.log(` ❌ FAILED: ${error}`);
console.log(` ⏱️ Time: ${totalTime}ms\n`);
throw error;
}
}
console.log(`${'═'.repeat(60)}`);
console.log('✅ ALL PAGES VERIFIED SUCCESSFULLY');
console.log(`${'═'.repeat(60)}`);
});