import { queryOne, execute, query } from '../../utils/db' import { requireAuth } from '../../utils/session' /** * 주간보고를 구글 그룹에 공유 (이메일 전송) * POST /api/google-group/share-report * * Gmail API로 그룹에 이메일 전송 * * Body: * - reportId: 주간보고 ID * - groupEmail: 그룹 이메일 주소 * - subject?: 이메일 제목 (기본값 자동 생성) */ export default defineEventHandler(async (event) => { const session = await requireAuth(event) const body = await readBody(event) const { reportId, groupEmail, subject } = body if (!reportId || !groupEmail) { throw createError({ statusCode: 400, message: '보고서 ID와 그룹 이메일이 필요합니다.' }) } // 주간보고 조회 const report = await queryOne(` SELECT r.*, e.employee_name, e.employee_email, p.project_name FROM wr_weekly_report r JOIN wr_employee_info e ON r.employee_id = e.employee_id LEFT JOIN wr_project_info p ON r.project_id = p.project_id WHERE r.report_id = $1 `, [reportId]) if (!report) { throw createError({ statusCode: 404, message: '주간보고를 찾을 수 없습니다.' }) } // 권한 확인 (본인 보고서만) if (report.employee_id !== session.employeeId) { throw createError({ statusCode: 403, message: '본인의 주간보고만 공유할 수 있습니다.' }) } // 사용자의 Google 토큰 조회 const employee = await queryOne(` SELECT google_access_token, google_refresh_token, google_token_expires_at, employee_email FROM wr_employee_info WHERE employee_id = $1 `, [session.employeeId]) if (!employee?.google_access_token) { throw createError({ statusCode: 401, message: 'Google 계정이 연결되지 않았습니다.' }) } let accessToken = employee.google_access_token // 토큰 만료 확인 및 갱신 if (employee.google_token_expires_at && new Date(employee.google_token_expires_at) < new Date()) { accessToken = await refreshGoogleToken(session.employeeId, employee.google_refresh_token) } // 이메일 내용 생성 const emailSubject = subject || `[주간보고] ${report.project_name || '개인'} - ${report.report_week}주차 (${report.employee_name})` const emailBody = generateReportEmailBody(report) // RFC 2822 형식의 이메일 메시지 생성 const emailLines = [ `From: ${employee.employee_email}`, `To: ${groupEmail}`, `Subject: =?UTF-8?B?${Buffer.from(emailSubject).toString('base64')}?=`, 'MIME-Version: 1.0', 'Content-Type: text/html; charset=UTF-8', '', emailBody ] const rawEmail = Buffer.from(emailLines.join('\r\n')) .toString('base64') .replace(/\+/g, '-') .replace(/\//g, '_') .replace(/=+$/, '') try { // Gmail API로 이메일 전송 const response = await $fetch('https://gmail.googleapis.com/gmail/v1/users/me/messages/send', { method: 'POST', headers: { 'Authorization': `Bearer ${accessToken}`, 'Content-Type': 'application/json' }, body: { raw: rawEmail } }) // 공유 이력 저장 await execute(` INSERT INTO wr_report_share_log (report_id, shared_to, shared_type, shared_by, message_id) VALUES ($1, $2, 'GOOGLE_GROUP', $3, $4) `, [reportId, groupEmail, session.employeeId, response.id]) return { success: true, message: `${groupEmail}로 주간보고가 공유되었습니다.`, messageId: response.id } } catch (e: any) { console.error('Gmail send error:', e) if (e.status === 403) { throw createError({ statusCode: 403, message: 'Gmail 발송 권한이 없습니다. Google 로그인 시 권한을 허용해주세요.' }) } throw createError({ statusCode: 500, message: '이메일 발송에 실패했습니다.' }) } }) /** * 주간보고 이메일 본문 생성 */ function generateReportEmailBody(report: any): string { const weekRange = `${report.week_start_date?.split('T')[0] || ''} ~ ${report.week_end_date?.split('T')[0] || ''}` return `

📋 주간업무보고

작성자 ${report.employee_name} 프로젝트 ${report.project_name || '-'}
보고 주차 ${report.report_year}년 ${report.report_week}주차 기간 ${weekRange}

✅ 금주 실적

${report.this_week_work || '(내용 없음)'}

📅 차주 계획

${report.next_week_plan || '(내용 없음)'}
${report.issues ? `

⚠️ 이슈사항

${report.issues}
` : ''} ${report.remarks ? `

📝 비고

${report.remarks}
` : ''}

이 메일은 주간업무보고 시스템에서 자동 발송되었습니다.

`.trim() } /** * Google 토큰 갱신 */ async function refreshGoogleToken(employeeId: number, refreshToken: string): Promise { const config = useRuntimeConfig() const response = await $fetch('https://oauth2.googleapis.com/token', { method: 'POST', body: new URLSearchParams({ client_id: config.googleClientId, client_secret: config.googleClientSecret, refresh_token: refreshToken, grant_type: 'refresh_token' }).toString(), headers: { 'Content-Type': 'application/x-www-form-urlencoded' } }) if (response.access_token) { await execute(` UPDATE wr_employee_info SET google_access_token = $1, google_token_expires_at = NOW() + INTERVAL '${response.expires_in} seconds' WHERE employee_id = $2 `, [response.access_token, employeeId]) return response.access_token } throw new Error('토큰 갱신 실패') }