추가
This commit is contained in:
@@ -9,14 +9,12 @@ let pool: pg.Pool | null = null
|
||||
*/
|
||||
export function getPool(): pg.Pool {
|
||||
if (!pool) {
|
||||
const config = useRuntimeConfig()
|
||||
|
||||
const poolConfig = {
|
||||
host: config.dbHost,
|
||||
port: parseInt(config.dbPort as string),
|
||||
database: config.dbName,
|
||||
user: config.dbUser,
|
||||
password: config.dbPassword,
|
||||
host: process.env.DB_HOST || 'localhost',
|
||||
port: parseInt(process.env.DB_PORT || '5432'),
|
||||
database: process.env.DB_NAME || 'weeklyreport',
|
||||
user: process.env.DB_USER || 'postgres',
|
||||
password: process.env.DB_PASSWORD || '',
|
||||
max: 10,
|
||||
idleTimeoutMillis: 30000,
|
||||
connectionTimeoutMillis: 2000,
|
||||
|
||||
33
backend/utils/ip.ts
Normal file
33
backend/utils/ip.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import type { H3Event } from 'h3'
|
||||
|
||||
/**
|
||||
* 클라이언트 IP 주소 가져오기
|
||||
*/
|
||||
export function getClientIp(event: H3Event): string {
|
||||
// 프록시/로드밸런서 뒤에 있을 경우
|
||||
const xForwardedFor = getHeader(event, 'x-forwarded-for')
|
||||
if (xForwardedFor) {
|
||||
return xForwardedFor.split(',')[0].trim()
|
||||
}
|
||||
|
||||
const xRealIp = getHeader(event, 'x-real-ip')
|
||||
if (xRealIp) {
|
||||
return xRealIp
|
||||
}
|
||||
|
||||
// 직접 연결
|
||||
const remoteAddress = event.node.req.socket?.remoteAddress
|
||||
if (remoteAddress) {
|
||||
// IPv6 localhost를 IPv4로 변환
|
||||
if (remoteAddress === '::1' || remoteAddress === '::ffff:127.0.0.1') {
|
||||
return '127.0.0.1'
|
||||
}
|
||||
// IPv6 매핑된 IPv4 주소 처리
|
||||
if (remoteAddress.startsWith('::ffff:')) {
|
||||
return remoteAddress.substring(7)
|
||||
}
|
||||
return remoteAddress
|
||||
}
|
||||
|
||||
return 'unknown'
|
||||
}
|
||||
@@ -1,99 +0,0 @@
|
||||
import { query, execute, insertReturning } from './db'
|
||||
import { getLastWeekInfo, formatDate } from './week-calc'
|
||||
|
||||
let isRunning = false
|
||||
|
||||
/**
|
||||
* 주간보고 취합 실행
|
||||
*/
|
||||
export async function aggregateWeeklyReports(targetYear?: number, targetWeek?: number) {
|
||||
const weekInfo = targetYear && targetWeek
|
||||
? { year: targetYear, week: targetWeek }
|
||||
: getLastWeekInfo()
|
||||
|
||||
console.log(`[Aggregator] 취합 시작: ${weekInfo.year}-W${weekInfo.week}`)
|
||||
|
||||
// 해당 주차에 제출된 보고서가 있는 프로젝트 조회
|
||||
const projects = await query<any>(`
|
||||
SELECT DISTINCT project_id
|
||||
FROM wr_weekly_report_detail
|
||||
WHERE report_year = $1 AND report_week = $2 AND report_status = 'SUBMITTED'
|
||||
`, [weekInfo.year, weekInfo.week])
|
||||
|
||||
let aggregatedCount = 0
|
||||
|
||||
for (const { project_id } of projects) {
|
||||
// 해당 프로젝트의 제출된 보고서들
|
||||
const reports = await query<any>(`
|
||||
SELECT report_id, work_hours
|
||||
FROM wr_weekly_report_detail
|
||||
WHERE project_id = $1 AND report_year = $2 AND report_week = $3
|
||||
AND report_status = 'SUBMITTED'
|
||||
`, [project_id, weekInfo.year, weekInfo.week])
|
||||
|
||||
const reportIds = reports.map((r: any) => r.report_id)
|
||||
const totalHours = reports.reduce((sum: number, r: any) => sum + (parseFloat(r.work_hours) || 0), 0)
|
||||
|
||||
// 주차 날짜 계산
|
||||
const jan4 = new Date(weekInfo.year, 0, 4)
|
||||
const firstMonday = new Date(jan4)
|
||||
firstMonday.setDate(jan4.getDate() - ((jan4.getDay() + 6) % 7))
|
||||
const targetMonday = new Date(firstMonday)
|
||||
targetMonday.setDate(firstMonday.getDate() + (weekInfo.week - 1) * 7)
|
||||
const targetSunday = new Date(targetMonday)
|
||||
targetSunday.setDate(targetMonday.getDate() + 6)
|
||||
|
||||
// UPSERT 취합 보고서
|
||||
await execute(`
|
||||
INSERT INTO wr_aggregated_report_summary (
|
||||
project_id, report_year, report_week,
|
||||
week_start_date, week_end_date,
|
||||
report_ids, member_count, total_work_hours
|
||||
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
|
||||
ON CONFLICT (project_id, report_year, report_week)
|
||||
DO UPDATE SET
|
||||
report_ids = $6,
|
||||
member_count = $7,
|
||||
total_work_hours = $8,
|
||||
aggregated_at = NOW(),
|
||||
updated_at = NOW()
|
||||
`, [
|
||||
project_id,
|
||||
weekInfo.year,
|
||||
weekInfo.week,
|
||||
formatDate(targetMonday),
|
||||
formatDate(targetSunday),
|
||||
reportIds,
|
||||
reportIds.length,
|
||||
totalHours || null
|
||||
])
|
||||
|
||||
// 개별 보고서 상태 변경
|
||||
await execute(`
|
||||
UPDATE wr_weekly_report_detail SET
|
||||
report_status = 'AGGREGATED',
|
||||
updated_at = NOW()
|
||||
WHERE report_id = ANY($1)
|
||||
`, [reportIds])
|
||||
|
||||
aggregatedCount++
|
||||
console.log(`[Aggregator] 프로젝트 ${project_id}: ${reportIds.length}건 취합`)
|
||||
}
|
||||
|
||||
console.log(`[Aggregator] 취합 완료: ${aggregatedCount}개 프로젝트`)
|
||||
|
||||
return {
|
||||
year: weekInfo.year,
|
||||
week: weekInfo.week,
|
||||
projectCount: aggregatedCount
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 스케줄러 상태
|
||||
*/
|
||||
export function getSchedulerStatus() {
|
||||
return {
|
||||
isRunning
|
||||
}
|
||||
}
|
||||
16
backend/utils/user.ts
Normal file
16
backend/utils/user.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import type { H3Event } from 'h3'
|
||||
import { queryOne } from './db'
|
||||
|
||||
/**
|
||||
* 현재 로그인한 사용자의 이메일 조회
|
||||
*/
|
||||
export async function getCurrentUserEmail(event: H3Event): Promise<string | null> {
|
||||
const userId = getCookie(event, 'user_id')
|
||||
if (!userId) return null
|
||||
|
||||
const user = await queryOne<{ employee_email: string }>(`
|
||||
SELECT employee_email FROM wr_employee_info WHERE employee_id = $1
|
||||
`, [parseInt(userId)])
|
||||
|
||||
return user?.employee_email || null
|
||||
}
|
||||
Reference in New Issue
Block a user