getCookie 제거
This commit is contained in:
5
app.vue
5
app.vue
@@ -1,3 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<NuxtPage />
|
<div>
|
||||||
|
<NuxtPage />
|
||||||
|
<ToastContainer />
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { query, execute, queryOne } from '../../utils/db'
|
import { query, execute, queryOne } from '../../utils/db'
|
||||||
|
import { requireAdmin } from '../../utils/session'
|
||||||
const ADMIN_EMAIL = 'coziny@gmail.com'
|
|
||||||
|
|
||||||
interface TaskInput {
|
interface TaskInput {
|
||||||
description: string
|
description: string
|
||||||
@@ -31,22 +30,15 @@ interface ReportInput {
|
|||||||
*/
|
*/
|
||||||
export default defineEventHandler(async (event) => {
|
export default defineEventHandler(async (event) => {
|
||||||
// 관리자 권한 체크
|
// 관리자 권한 체크
|
||||||
const userId = getCookie(event, 'user_id')
|
const userId = await requireAdmin(event)
|
||||||
if (!userId) {
|
|
||||||
throw createError({ statusCode: 401, message: '로그인이 필요합니다.' })
|
|
||||||
}
|
|
||||||
|
|
||||||
const clientIp = getHeader(event, 'x-forwarded-for') || 'unknown'
|
const clientIp = getHeader(event, 'x-forwarded-for') || 'unknown'
|
||||||
|
|
||||||
const currentUser = await query<any>(`
|
// 관리자 이메일 조회
|
||||||
|
const currentUser = await queryOne<any>(`
|
||||||
SELECT employee_email FROM wr_employee_info WHERE employee_id = $1
|
SELECT employee_email FROM wr_employee_info WHERE employee_id = $1
|
||||||
`, [userId])
|
`, [userId])
|
||||||
|
const adminEmail = currentUser?.employee_email || ''
|
||||||
if (!currentUser[0] || currentUser[0].employee_email !== ADMIN_EMAIL) {
|
|
||||||
throw createError({ statusCode: 403, message: '관리자만 사용할 수 있습니다.' })
|
|
||||||
}
|
|
||||||
|
|
||||||
const adminEmail = currentUser[0].employee_email
|
|
||||||
|
|
||||||
const body = await readBody<{
|
const body = await readBody<{
|
||||||
reportYear: number
|
reportYear: number
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { query } from '../../utils/db'
|
import { query } from '../../utils/db'
|
||||||
import { callOpenAIVision, REPORT_PARSE_SYSTEM_PROMPT } from '../../utils/openai'
|
import { callOpenAIVision, REPORT_PARSE_SYSTEM_PROMPT } from '../../utils/openai'
|
||||||
|
import { requireAdmin } from '../../utils/session'
|
||||||
const ADMIN_EMAIL = 'coziny@gmail.com'
|
|
||||||
|
|
||||||
interface ParsedTask {
|
interface ParsedTask {
|
||||||
description: string
|
description: string
|
||||||
@@ -37,18 +36,7 @@ interface ParsedResult {
|
|||||||
*/
|
*/
|
||||||
export default defineEventHandler(async (event) => {
|
export default defineEventHandler(async (event) => {
|
||||||
// 관리자 권한 체크
|
// 관리자 권한 체크
|
||||||
const userId = getCookie(event, 'user_id')
|
await requireAdmin(event)
|
||||||
if (!userId) {
|
|
||||||
throw createError({ statusCode: 401, message: '로그인이 필요합니다.' })
|
|
||||||
}
|
|
||||||
|
|
||||||
const currentUser = await query<any>(`
|
|
||||||
SELECT employee_email FROM wr_employee_info WHERE employee_id = $1
|
|
||||||
`, [userId])
|
|
||||||
|
|
||||||
if (!currentUser[0] || currentUser[0].employee_email !== ADMIN_EMAIL) {
|
|
||||||
throw createError({ statusCode: 403, message: '관리자만 사용할 수 있습니다.' })
|
|
||||||
}
|
|
||||||
|
|
||||||
const body = await readBody<{ images: string[] }>(event)
|
const body = await readBody<{ images: string[] }>(event)
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { query } from '../../utils/db'
|
import { query } from '../../utils/db'
|
||||||
import { callOpenAI, buildParseReportPrompt } from '../../utils/openai'
|
import { callOpenAI, buildParseReportPrompt } from '../../utils/openai'
|
||||||
|
import { requireAdmin } from '../../utils/session'
|
||||||
const ADMIN_EMAIL = 'coziny@gmail.com'
|
|
||||||
|
|
||||||
interface ParsedTask {
|
interface ParsedTask {
|
||||||
description: string
|
description: string
|
||||||
@@ -37,18 +36,7 @@ interface ParsedResult {
|
|||||||
*/
|
*/
|
||||||
export default defineEventHandler(async (event) => {
|
export default defineEventHandler(async (event) => {
|
||||||
// 관리자 권한 체크
|
// 관리자 권한 체크
|
||||||
const userId = getCookie(event, 'user_id')
|
await requireAdmin(event)
|
||||||
if (!userId) {
|
|
||||||
throw createError({ statusCode: 401, message: '로그인이 필요합니다.' })
|
|
||||||
}
|
|
||||||
|
|
||||||
const currentUser = await query<any>(`
|
|
||||||
SELECT employee_email FROM wr_employee_info WHERE employee_id = $1
|
|
||||||
`, [userId])
|
|
||||||
|
|
||||||
if (!currentUser[0] || currentUser[0].employee_email !== ADMIN_EMAIL) {
|
|
||||||
throw createError({ statusCode: 403, message: '관리자만 사용할 수 있습니다.' })
|
|
||||||
}
|
|
||||||
|
|
||||||
const body = await readBody<{ rawText: string }>(event)
|
const body = await readBody<{ rawText: string }>(event)
|
||||||
|
|
||||||
|
|||||||
@@ -1,15 +1,13 @@
|
|||||||
import { query } from '../../utils/db'
|
import { query } from '../../utils/db'
|
||||||
import { callOpenAIVision } from '../../utils/openai'
|
import { callOpenAIVision } from '../../utils/openai'
|
||||||
|
import { requireAuth } from '../../utils/session'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 개인 주간보고 이미지 분석 (OpenAI Vision)
|
* 개인 주간보고 이미지 분석 (OpenAI Vision)
|
||||||
* POST /api/ai/parse-my-report-image
|
* POST /api/ai/parse-my-report-image
|
||||||
*/
|
*/
|
||||||
export default defineEventHandler(async (event) => {
|
export default defineEventHandler(async (event) => {
|
||||||
const userId = getCookie(event, 'user_id')
|
const userId = await requireAuth(event)
|
||||||
if (!userId) {
|
|
||||||
throw createError({ statusCode: 401, message: '로그인이 필요합니다.' })
|
|
||||||
}
|
|
||||||
|
|
||||||
const body = await readBody<{ images: string[] }>(event)
|
const body = await readBody<{ images: string[] }>(event)
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { query } from '../../utils/db'
|
import { query } from '../../utils/db'
|
||||||
import { callOpenAI } from '../../utils/openai'
|
import { callOpenAI } from '../../utils/openai'
|
||||||
|
import { requireAuth } from '../../utils/session'
|
||||||
|
|
||||||
interface ParsedTask {
|
interface ParsedTask {
|
||||||
description: string
|
description: string
|
||||||
@@ -26,10 +27,7 @@ interface ParsedResult {
|
|||||||
* POST /api/ai/parse-my-report
|
* POST /api/ai/parse-my-report
|
||||||
*/
|
*/
|
||||||
export default defineEventHandler(async (event) => {
|
export default defineEventHandler(async (event) => {
|
||||||
const userId = getCookie(event, 'user_id')
|
const userId = await requireAuth(event)
|
||||||
if (!userId) {
|
|
||||||
throw createError({ statusCode: 401, message: '로그인이 필요합니다.' })
|
|
||||||
}
|
|
||||||
|
|
||||||
const body = await readBody<{ rawText: string }>(event)
|
const body = await readBody<{ rawText: string }>(event)
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { query } from '../../utils/db'
|
import { query } from '../../utils/db'
|
||||||
|
import { requireAuth } from '../../utils/session'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 대시보드 통계 API
|
* 대시보드 통계 API
|
||||||
@@ -9,10 +10,7 @@ import { query } from '../../utils/db'
|
|||||||
* - 제출 현황
|
* - 제출 현황
|
||||||
*/
|
*/
|
||||||
export default defineEventHandler(async (event) => {
|
export default defineEventHandler(async (event) => {
|
||||||
const userId = getCookie(event, 'user_id')
|
const userId = await requireAuth(event)
|
||||||
if (!userId) {
|
|
||||||
throw createError({ statusCode: 401, message: '로그인이 필요합니다.' })
|
|
||||||
}
|
|
||||||
|
|
||||||
const q = getQuery(event)
|
const q = getQuery(event)
|
||||||
const year = parseInt(q.year as string) || new Date().getFullYear()
|
const year = parseInt(q.year as string) || new Date().getFullYear()
|
||||||
|
|||||||
@@ -1,12 +1,19 @@
|
|||||||
import { queryOne, query } from '../../../utils/db'
|
import { queryOne, query } from '../../../utils/db'
|
||||||
|
import { requireAuth, getSessionIdFromCookie, getDbSession } from '../../../utils/session'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 직원 상세 조회
|
* 직원 상세 조회
|
||||||
* GET /api/employee/[id]/detail
|
* GET /api/employee/[id]/detail
|
||||||
*/
|
*/
|
||||||
export default defineEventHandler(async (event) => {
|
export default defineEventHandler(async (event) => {
|
||||||
|
await requireAuth(event)
|
||||||
|
|
||||||
const employeeId = getRouterParam(event, 'id')
|
const employeeId = getRouterParam(event, 'id')
|
||||||
const currentHistoryId = getCookie(event, 'login_history_id')
|
|
||||||
|
// 세션에서 현재 로그인 히스토리 ID 가져오기
|
||||||
|
const sessionId = getSessionIdFromCookie(event)
|
||||||
|
const session = sessionId ? await getDbSession(sessionId) : null
|
||||||
|
const currentHistoryId = session?.loginHistoryId || null
|
||||||
|
|
||||||
const employee = await queryOne<any>(`
|
const employee = await queryOne<any>(`
|
||||||
SELECT * FROM wr_employee_info WHERE employee_id = $1
|
SELECT * FROM wr_employee_info WHERE employee_id = $1
|
||||||
|
|||||||
@@ -1,14 +1,12 @@
|
|||||||
import { query, execute } from '../../../utils/db'
|
import { query, execute } from '../../../utils/db'
|
||||||
|
import { requireAuth } from '../../../utils/session'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 개선의견 삭제
|
* 개선의견 삭제
|
||||||
* DELETE /api/feedback/[id]/delete
|
* DELETE /api/feedback/[id]/delete
|
||||||
*/
|
*/
|
||||||
export default defineEventHandler(async (event) => {
|
export default defineEventHandler(async (event) => {
|
||||||
const userId = getCookie(event, 'user_id')
|
const userId = await requireAuth(event)
|
||||||
if (!userId) {
|
|
||||||
throw createError({ statusCode: 401, message: '로그인이 필요합니다.' })
|
|
||||||
}
|
|
||||||
|
|
||||||
const feedbackId = getRouterParam(event, 'id')
|
const feedbackId = getRouterParam(event, 'id')
|
||||||
if (!feedbackId) {
|
if (!feedbackId) {
|
||||||
@@ -24,7 +22,7 @@ export default defineEventHandler(async (event) => {
|
|||||||
throw createError({ statusCode: 404, message: '의견을 찾을 수 없습니다.' })
|
throw createError({ statusCode: 404, message: '의견을 찾을 수 없습니다.' })
|
||||||
}
|
}
|
||||||
|
|
||||||
if (feedback[0].author_id !== parseInt(userId)) {
|
if (feedback[0].author_id !== userId) {
|
||||||
throw createError({ statusCode: 403, message: '본인의 의견만 삭제할 수 있습니다.' })
|
throw createError({ statusCode: 403, message: '본인의 의견만 삭제할 수 있습니다.' })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,14 +1,12 @@
|
|||||||
import { query, execute, queryOne } from '../../../utils/db'
|
import { query, execute, queryOne } from '../../../utils/db'
|
||||||
|
import { requireAuth } from '../../../utils/session'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 개선의견 공감 토글
|
* 개선의견 공감 토글
|
||||||
* POST /api/feedback/[id]/like
|
* POST /api/feedback/[id]/like
|
||||||
*/
|
*/
|
||||||
export default defineEventHandler(async (event) => {
|
export default defineEventHandler(async (event) => {
|
||||||
const userId = getCookie(event, 'user_id')
|
const userId = await requireAuth(event)
|
||||||
if (!userId) {
|
|
||||||
throw createError({ statusCode: 401, message: '로그인이 필요합니다.' })
|
|
||||||
}
|
|
||||||
|
|
||||||
const feedbackId = getRouterParam(event, 'id')
|
const feedbackId = getRouterParam(event, 'id')
|
||||||
if (!feedbackId) {
|
if (!feedbackId) {
|
||||||
|
|||||||
@@ -1,14 +1,12 @@
|
|||||||
import { query, execute } from '../../../utils/db'
|
import { query, execute } from '../../../utils/db'
|
||||||
|
import { requireAuth } from '../../../utils/session'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 개선의견 수정
|
* 개선의견 수정
|
||||||
* PUT /api/feedback/[id]/update
|
* PUT /api/feedback/[id]/update
|
||||||
*/
|
*/
|
||||||
export default defineEventHandler(async (event) => {
|
export default defineEventHandler(async (event) => {
|
||||||
const userId = getCookie(event, 'user_id')
|
const userId = await requireAuth(event)
|
||||||
if (!userId) {
|
|
||||||
throw createError({ statusCode: 401, message: '로그인이 필요합니다.' })
|
|
||||||
}
|
|
||||||
|
|
||||||
const feedbackId = getRouterParam(event, 'id')
|
const feedbackId = getRouterParam(event, 'id')
|
||||||
if (!feedbackId) {
|
if (!feedbackId) {
|
||||||
@@ -24,7 +22,7 @@ export default defineEventHandler(async (event) => {
|
|||||||
throw createError({ statusCode: 404, message: '의견을 찾을 수 없습니다.' })
|
throw createError({ statusCode: 404, message: '의견을 찾을 수 없습니다.' })
|
||||||
}
|
}
|
||||||
|
|
||||||
if (feedback[0].author_id !== parseInt(userId)) {
|
if (feedback[0].author_id !== userId) {
|
||||||
throw createError({ statusCode: 403, message: '본인의 의견만 수정할 수 있습니다.' })
|
throw createError({ statusCode: 403, message: '본인의 의견만 수정할 수 있습니다.' })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,14 +1,12 @@
|
|||||||
import { query, queryOne } from '../../utils/db'
|
import { query, queryOne } from '../../utils/db'
|
||||||
|
import { requireAuth } from '../../utils/session'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 개선의견 작성
|
* 개선의견 작성
|
||||||
* POST /api/feedback/create
|
* POST /api/feedback/create
|
||||||
*/
|
*/
|
||||||
export default defineEventHandler(async (event) => {
|
export default defineEventHandler(async (event) => {
|
||||||
const userId = getCookie(event, 'user_id')
|
const userId = await requireAuth(event)
|
||||||
if (!userId) {
|
|
||||||
throw createError({ statusCode: 401, message: '로그인이 필요합니다.' })
|
|
||||||
}
|
|
||||||
|
|
||||||
const body = await readBody<{
|
const body = await readBody<{
|
||||||
category: string
|
category: string
|
||||||
|
|||||||
@@ -1,14 +1,12 @@
|
|||||||
import { query } from '../../utils/db'
|
import { query } from '../../utils/db'
|
||||||
|
import { requireAuth } from '../../utils/session'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 개선의견 목록 조회
|
* 개선의견 목록 조회
|
||||||
* GET /api/feedback/list
|
* GET /api/feedback/list
|
||||||
*/
|
*/
|
||||||
export default defineEventHandler(async (event) => {
|
export default defineEventHandler(async (event) => {
|
||||||
const userId = getCookie(event, 'user_id')
|
const userId = await requireAuth(event)
|
||||||
if (!userId) {
|
|
||||||
throw createError({ statusCode: 401, message: '로그인이 필요합니다.' })
|
|
||||||
}
|
|
||||||
|
|
||||||
const q = getQuery(event)
|
const q = getQuery(event)
|
||||||
const page = parseInt(q.page as string) || 1
|
const page = parseInt(q.page as string) || 1
|
||||||
@@ -91,7 +89,7 @@ export default defineEventHandler(async (event) => {
|
|||||||
createdAt: f.created_at,
|
createdAt: f.created_at,
|
||||||
updatedAt: f.updated_at,
|
updatedAt: f.updated_at,
|
||||||
isLiked: f.is_liked,
|
isLiked: f.is_liked,
|
||||||
isOwner: f.author_id === parseInt(userId)
|
isOwner: f.author_id === userId
|
||||||
})),
|
})),
|
||||||
pagination: {
|
pagination: {
|
||||||
page,
|
page,
|
||||||
|
|||||||
@@ -1,14 +1,12 @@
|
|||||||
import { query } from '../../utils/db'
|
import { query } from '../../utils/db'
|
||||||
|
import { requireAuth } from '../../utils/session'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 내가 보고서 작성한 프로젝트 목록
|
* 내가 보고서 작성한 프로젝트 목록
|
||||||
* GET /api/project/my-projects
|
* GET /api/project/my-projects
|
||||||
*/
|
*/
|
||||||
export default defineEventHandler(async (event) => {
|
export default defineEventHandler(async (event) => {
|
||||||
const userId = getCookie(event, 'user_id')
|
const userId = await requireAuth(event)
|
||||||
if (!userId) {
|
|
||||||
throw createError({ statusCode: 401, message: '로그인이 필요합니다.' })
|
|
||||||
}
|
|
||||||
|
|
||||||
// 내가 주간보고를 작성한 프로젝트 + 전체 활성 프로젝트
|
// 내가 주간보고를 작성한 프로젝트 + 전체 활성 프로젝트
|
||||||
const projects = await query(`
|
const projects = await query(`
|
||||||
@@ -23,7 +21,7 @@ export default defineEventHandler(async (event) => {
|
|||||||
) t ON p.project_id = t.project_id
|
) t ON p.project_id = t.project_id
|
||||||
WHERE p.project_status = 'ACTIVE'
|
WHERE p.project_status = 'ACTIVE'
|
||||||
ORDER BY has_my_report DESC, p.project_name
|
ORDER BY has_my_report DESC, p.project_name
|
||||||
`, [parseInt(userId)])
|
`, [userId])
|
||||||
|
|
||||||
return projects.map((p: any) => ({
|
return projects.map((p: any) => ({
|
||||||
projectId: p.project_id,
|
projectId: p.project_id,
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { execute, queryOne } from '../../../../utils/db'
|
import { execute, queryOne } from '../../../../utils/db'
|
||||||
|
import { requireAuth } from '../../../../utils/session'
|
||||||
|
|
||||||
interface ReviewBody {
|
interface ReviewBody {
|
||||||
reviewerComment?: string
|
reviewerComment?: string
|
||||||
@@ -9,10 +10,7 @@ interface ReviewBody {
|
|||||||
* PUT /api/report/summary/[id]/review
|
* PUT /api/report/summary/[id]/review
|
||||||
*/
|
*/
|
||||||
export default defineEventHandler(async (event) => {
|
export default defineEventHandler(async (event) => {
|
||||||
const userId = getCookie(event, 'user_id')
|
const userId = await requireAuth(event)
|
||||||
if (!userId) {
|
|
||||||
throw createError({ statusCode: 401, message: '로그인이 필요합니다.' })
|
|
||||||
}
|
|
||||||
|
|
||||||
const summaryId = getRouterParam(event, 'id')
|
const summaryId = getRouterParam(event, 'id')
|
||||||
const body = await readBody<ReviewBody>(event)
|
const body = await readBody<ReviewBody>(event)
|
||||||
@@ -33,7 +31,7 @@ export default defineEventHandler(async (event) => {
|
|||||||
summary_status = 'REVIEWED',
|
summary_status = 'REVIEWED',
|
||||||
updated_at = NOW()
|
updated_at = NOW()
|
||||||
WHERE summary_id = $3
|
WHERE summary_id = $3
|
||||||
`, [parseInt(userId), body.reviewerComment || null, summaryId])
|
`, [userId, body.reviewerComment || null, summaryId])
|
||||||
|
|
||||||
return { success: true }
|
return { success: true }
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import { defineEventHandler, readBody, createError, getCookie } from 'h3'
|
import { defineEventHandler, readBody, createError } from 'h3'
|
||||||
import { query, queryOne, execute, insertReturning } from '../../../utils/db'
|
import { query, queryOne, execute, insertReturning } from '../../../utils/db'
|
||||||
import { getClientIp } from '../../../utils/ip'
|
import { getClientIp } from '../../../utils/ip'
|
||||||
import { getCurrentUserEmail } from '../../../utils/user'
|
import { getCurrentUserEmail } from '../../../utils/user'
|
||||||
|
import { requireAuth } from '../../../utils/session'
|
||||||
import OpenAI from 'openai'
|
import OpenAI from 'openai'
|
||||||
|
|
||||||
interface AggregateBody {
|
interface AggregateBody {
|
||||||
@@ -19,10 +20,7 @@ const openai = new OpenAI({
|
|||||||
* POST /api/report/summary/aggregate
|
* POST /api/report/summary/aggregate
|
||||||
*/
|
*/
|
||||||
export default defineEventHandler(async (event) => {
|
export default defineEventHandler(async (event) => {
|
||||||
const userId = getCookie(event, 'user_id')
|
const userId = await requireAuth(event)
|
||||||
if (!userId) {
|
|
||||||
throw createError({ statusCode: 401, message: '로그인이 필요합니다.' })
|
|
||||||
}
|
|
||||||
|
|
||||||
const body = await readBody<AggregateBody>(event)
|
const body = await readBody<AggregateBody>(event)
|
||||||
const clientIp = getClientIp(event)
|
const clientIp = getClientIp(event)
|
||||||
|
|||||||
@@ -1,12 +1,9 @@
|
|||||||
import { defineEventHandler, getQuery, createError, getCookie } from 'h3'
|
import { defineEventHandler, getQuery, createError } from 'h3'
|
||||||
import { query } from '../../../utils/db'
|
import { query } from '../../../utils/db'
|
||||||
|
import { requireAuth } from '../../../utils/session'
|
||||||
|
|
||||||
export default defineEventHandler(async (event) => {
|
export default defineEventHandler(async (event) => {
|
||||||
const userId = getCookie(event, 'user_id')
|
const userId = await requireAuth(event)
|
||||||
|
|
||||||
if (!userId) {
|
|
||||||
throw createError({ statusCode: 401, message: '로그인이 필요합니다.' })
|
|
||||||
}
|
|
||||||
|
|
||||||
const { year, week } = getQuery(event)
|
const { year, week } = getQuery(event)
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { defineEventHandler, createError, getCookie } from 'h3'
|
import { defineEventHandler, createError } from 'h3'
|
||||||
import { query, queryOne, execute } from '../../../utils/db'
|
import { query, queryOne, execute } from '../../../utils/db'
|
||||||
|
import { requireAuth } from '../../../utils/session'
|
||||||
import OpenAI from 'openai'
|
import OpenAI from 'openai'
|
||||||
|
|
||||||
const openai = new OpenAI({
|
const openai = new OpenAI({
|
||||||
@@ -11,10 +12,7 @@ const openai = new OpenAI({
|
|||||||
* POST /api/report/summary/regenerate-ai
|
* POST /api/report/summary/regenerate-ai
|
||||||
*/
|
*/
|
||||||
export default defineEventHandler(async (event) => {
|
export default defineEventHandler(async (event) => {
|
||||||
const userId = getCookie(event, 'user_id')
|
const userId = await requireAuth(event)
|
||||||
if (!userId) {
|
|
||||||
throw createError({ statusCode: 401, message: '로그인이 필요합니다.' })
|
|
||||||
}
|
|
||||||
|
|
||||||
// AI 요약이 없는 취합 보고서 조회
|
// AI 요약이 없는 취합 보고서 조회
|
||||||
const summaries = await query<any>(`
|
const summaries = await query<any>(`
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { query, execute } from '../../../../utils/db'
|
import { query, execute } from '../../../../utils/db'
|
||||||
|
import { requireAuth } from '../../../../utils/session'
|
||||||
|
|
||||||
const ADMIN_EMAIL = 'coziny@gmail.com'
|
const ADMIN_EMAIL = 'coziny@gmail.com'
|
||||||
|
|
||||||
@@ -7,10 +8,7 @@ const ADMIN_EMAIL = 'coziny@gmail.com'
|
|||||||
* DELETE /api/report/weekly/[id]/delete
|
* DELETE /api/report/weekly/[id]/delete
|
||||||
*/
|
*/
|
||||||
export default defineEventHandler(async (event) => {
|
export default defineEventHandler(async (event) => {
|
||||||
const userId = getCookie(event, 'user_id')
|
const userId = await requireAuth(event)
|
||||||
if (!userId) {
|
|
||||||
throw createError({ statusCode: 401, message: '로그인이 필요합니다.' })
|
|
||||||
}
|
|
||||||
|
|
||||||
const reportId = getRouterParam(event, 'id')
|
const reportId = getRouterParam(event, 'id')
|
||||||
if (!reportId) {
|
if (!reportId) {
|
||||||
@@ -33,7 +31,7 @@ export default defineEventHandler(async (event) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 권한 체크: 본인 또는 관리자만 삭제 가능
|
// 권한 체크: 본인 또는 관리자만 삭제 가능
|
||||||
if (report[0].author_id !== parseInt(userId) && !isAdmin) {
|
if (report[0].author_id !== userId && !isAdmin) {
|
||||||
throw createError({ statusCode: 403, message: '삭제 권한이 없습니다.' })
|
throw createError({ statusCode: 403, message: '삭제 권한이 없습니다.' })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,14 +1,12 @@
|
|||||||
import { query, queryOne } from '../../../../utils/db'
|
import { query, queryOne } from '../../../../utils/db'
|
||||||
|
import { requireAuth } from '../../../../utils/session'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 주간보고 상세 조회
|
* 주간보고 상세 조회
|
||||||
* GET /api/report/weekly/[id]/detail
|
* GET /api/report/weekly/[id]/detail
|
||||||
*/
|
*/
|
||||||
export default defineEventHandler(async (event) => {
|
export default defineEventHandler(async (event) => {
|
||||||
const userId = getCookie(event, 'user_id')
|
const userId = await requireAuth(event)
|
||||||
if (!userId) {
|
|
||||||
throw createError({ statusCode: 401, message: '로그인이 필요합니다.' })
|
|
||||||
}
|
|
||||||
|
|
||||||
const reportId = getRouterParam(event, 'id')
|
const reportId = getRouterParam(event, 'id')
|
||||||
|
|
||||||
|
|||||||
@@ -1,16 +1,14 @@
|
|||||||
import { execute, queryOne } from '../../../../utils/db'
|
import { execute, queryOne } from '../../../../utils/db'
|
||||||
import { getClientIp } from '../../../../utils/ip'
|
import { getClientIp } from '../../../../utils/ip'
|
||||||
import { getCurrentUserEmail } from '../../../../utils/user'
|
import { getCurrentUserEmail } from '../../../../utils/user'
|
||||||
|
import { requireAuth } from '../../../../utils/session'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 주간보고 제출
|
* 주간보고 제출
|
||||||
* POST /api/report/weekly/[id]/submit
|
* POST /api/report/weekly/[id]/submit
|
||||||
*/
|
*/
|
||||||
export default defineEventHandler(async (event) => {
|
export default defineEventHandler(async (event) => {
|
||||||
const userId = getCookie(event, 'user_id')
|
const userId = await requireAuth(event)
|
||||||
if (!userId) {
|
|
||||||
throw createError({ statusCode: 401, message: '로그인이 필요합니다.' })
|
|
||||||
}
|
|
||||||
|
|
||||||
const reportId = getRouterParam(event, 'id')
|
const reportId = getRouterParam(event, 'id')
|
||||||
const clientIp = getClientIp(event)
|
const clientIp = getClientIp(event)
|
||||||
@@ -25,7 +23,7 @@ export default defineEventHandler(async (event) => {
|
|||||||
throw createError({ statusCode: 404, message: '보고서를 찾을 수 없습니다.' })
|
throw createError({ statusCode: 404, message: '보고서를 찾을 수 없습니다.' })
|
||||||
}
|
}
|
||||||
|
|
||||||
if (report.author_id !== parseInt(userId)) {
|
if (report.author_id !== userId) {
|
||||||
throw createError({ statusCode: 403, message: '본인의 보고서만 제출할 수 있습니다.' })
|
throw createError({ statusCode: 403, message: '본인의 보고서만 제출할 수 있습니다.' })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { query, execute, queryOne } from '../../../../utils/db'
|
import { query, execute, queryOne } from '../../../../utils/db'
|
||||||
|
import { requireAuth } from '../../../../utils/session'
|
||||||
|
|
||||||
const ADMIN_EMAIL = 'coziny@gmail.com'
|
const ADMIN_EMAIL = 'coziny@gmail.com'
|
||||||
|
|
||||||
@@ -7,10 +8,7 @@ const ADMIN_EMAIL = 'coziny@gmail.com'
|
|||||||
* PUT /api/report/weekly/[id]/update
|
* PUT /api/report/weekly/[id]/update
|
||||||
*/
|
*/
|
||||||
export default defineEventHandler(async (event) => {
|
export default defineEventHandler(async (event) => {
|
||||||
const userId = getCookie(event, 'user_id')
|
const userId = await requireAuth(event)
|
||||||
if (!userId) {
|
|
||||||
throw createError({ statusCode: 401, message: '로그인이 필요합니다.' })
|
|
||||||
}
|
|
||||||
|
|
||||||
const reportId = getRouterParam(event, 'id')
|
const reportId = getRouterParam(event, 'id')
|
||||||
const clientIp = getHeader(event, 'x-forwarded-for') || 'unknown'
|
const clientIp = getHeader(event, 'x-forwarded-for') || 'unknown'
|
||||||
@@ -28,7 +26,7 @@ export default defineEventHandler(async (event) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 관리자가 아니면 본인 보고서만 수정 가능
|
// 관리자가 아니면 본인 보고서만 수정 가능
|
||||||
if (!isAdmin && report.author_id !== parseInt(userId)) {
|
if (!isAdmin && report.author_id !== userId) {
|
||||||
throw createError({ statusCode: 403, message: '본인의 보고서만 수정할 수 있습니다.' })
|
throw createError({ statusCode: 403, message: '본인의 보고서만 수정할 수 있습니다.' })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,14 +1,13 @@
|
|||||||
import { query, execute, queryOne } from '../../../utils/db'
|
import { query, execute, queryOne } from '../../../utils/db'
|
||||||
|
import { requireAuth } from '../../../utils/session'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 주간보고 작성
|
* 주간보고 작성
|
||||||
* POST /api/report/weekly/create
|
* POST /api/report/weekly/create
|
||||||
*/
|
*/
|
||||||
export default defineEventHandler(async (event) => {
|
export default defineEventHandler(async (event) => {
|
||||||
const userId = getCookie(event, 'user_id')
|
// 세션 기반 인증 사용 (레거시 쿠키 대신)
|
||||||
if (!userId) {
|
const userId = await requireAuth(event)
|
||||||
throw createError({ statusCode: 401, message: '로그인이 필요합니다.' })
|
|
||||||
}
|
|
||||||
|
|
||||||
const clientIp = getHeader(event, 'x-forwarded-for') || 'unknown'
|
const clientIp = getHeader(event, 'x-forwarded-for') || 'unknown'
|
||||||
const user = await queryOne<any>(`SELECT employee_email FROM wr_employee_info WHERE employee_id = $1`, [userId])
|
const user = await queryOne<any>(`SELECT employee_email FROM wr_employee_info WHERE employee_id = $1`, [userId])
|
||||||
|
|||||||
@@ -1,15 +1,13 @@
|
|||||||
import { query } from '../../../utils/db'
|
import { query } from '../../../utils/db'
|
||||||
import { getWeekInfo, formatWeekString } from '../../../utils/week-calc'
|
import { getWeekInfo, formatWeekString } from '../../../utils/week-calc'
|
||||||
|
import { requireAuth } from '../../../utils/session'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 이번 주 보고서 현황 조회
|
* 이번 주 보고서 현황 조회
|
||||||
* GET /api/report/weekly/current-week
|
* GET /api/report/weekly/current-week
|
||||||
*/
|
*/
|
||||||
export default defineEventHandler(async (event) => {
|
export default defineEventHandler(async (event) => {
|
||||||
const userId = getCookie(event, 'user_id')
|
const userId = await requireAuth(event)
|
||||||
if (!userId) {
|
|
||||||
throw createError({ statusCode: 401, message: '로그인이 필요합니다.' })
|
|
||||||
}
|
|
||||||
|
|
||||||
const weekInfo = getWeekInfo()
|
const weekInfo = getWeekInfo()
|
||||||
|
|
||||||
@@ -20,7 +18,7 @@ export default defineEventHandler(async (event) => {
|
|||||||
JOIN wr_project_info p ON r.project_id = p.project_id
|
JOIN wr_project_info p ON r.project_id = p.project_id
|
||||||
WHERE r.author_id = $1 AND r.report_year = $2 AND r.report_week = $3
|
WHERE r.author_id = $1 AND r.report_year = $2 AND r.report_week = $3
|
||||||
ORDER BY p.project_name
|
ORDER BY p.project_name
|
||||||
`, [parseInt(userId), weekInfo.year, weekInfo.week])
|
`, [userId, weekInfo.year, weekInfo.week])
|
||||||
|
|
||||||
return {
|
return {
|
||||||
weekInfo: {
|
weekInfo: {
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { query } from '../../../utils/db'
|
import { query } from '../../../utils/db'
|
||||||
|
import { requireAuth } from '../../../utils/session'
|
||||||
|
|
||||||
const ADMIN_EMAIL = 'coziny@gmail.com'
|
const ADMIN_EMAIL = 'coziny@gmail.com'
|
||||||
|
|
||||||
@@ -19,10 +20,8 @@ const ADMIN_EMAIL = 'coziny@gmail.com'
|
|||||||
* - limit: 조회 개수 (기본 100)
|
* - limit: 조회 개수 (기본 100)
|
||||||
*/
|
*/
|
||||||
export default defineEventHandler(async (event) => {
|
export default defineEventHandler(async (event) => {
|
||||||
const userId = getCookie(event, 'user_id')
|
// 세션 기반 인증 사용
|
||||||
if (!userId) {
|
const userId = await requireAuth(event)
|
||||||
throw createError({ statusCode: 401, message: '로그인이 필요합니다.' })
|
|
||||||
}
|
|
||||||
|
|
||||||
// 현재 사용자 정보 조회 (관리자 여부 확인)
|
// 현재 사용자 정보 조회 (관리자 여부 확인)
|
||||||
const currentUser = await query<any>(`
|
const currentUser = await query<any>(`
|
||||||
|
|||||||
@@ -1,16 +1,17 @@
|
|||||||
import type { H3Event } from 'h3'
|
import type { H3Event } from 'h3'
|
||||||
import { queryOne } from './db'
|
import { queryOne } from './db'
|
||||||
|
import { getAuthenticatedUserId } from './session'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 현재 로그인한 사용자의 이메일 조회
|
* 현재 로그인한 사용자의 이메일 조회
|
||||||
*/
|
*/
|
||||||
export async function getCurrentUserEmail(event: H3Event): Promise<string | null> {
|
export async function getCurrentUserEmail(event: H3Event): Promise<string | null> {
|
||||||
const userId = getCookie(event, 'user_id')
|
const userId = await getAuthenticatedUserId(event)
|
||||||
if (!userId) return null
|
if (!userId) return null
|
||||||
|
|
||||||
const user = await queryOne<{ employee_email: string }>(`
|
const user = await queryOne<{ employee_email: string }>(`
|
||||||
SELECT employee_email FROM wr_employee_info WHERE employee_id = $1
|
SELECT employee_email FROM wr_employee_info WHERE employee_id = $1
|
||||||
`, [parseInt(userId)])
|
`, [userId])
|
||||||
|
|
||||||
return user?.employee_email || null
|
return user?.employee_email || null
|
||||||
}
|
}
|
||||||
|
|||||||
49
frontend/components/common/ToastContainer.vue
Normal file
49
frontend/components/common/ToastContainer.vue
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
<template>
|
||||||
|
<div class="toast-container position-fixed top-0 end-0 p-3" style="z-index: 1100;">
|
||||||
|
<div
|
||||||
|
v-for="toast in toasts"
|
||||||
|
:key="toast.id"
|
||||||
|
class="toast show"
|
||||||
|
role="alert"
|
||||||
|
>
|
||||||
|
<div class="toast-header" :class="headerClass(toast.type)">
|
||||||
|
<i :class="iconClass(toast.type)" class="me-2"></i>
|
||||||
|
<strong class="me-auto">{{ toast.title }}</strong>
|
||||||
|
<button type="button" class="btn-close btn-close-white" @click="remove(toast.id)"></button>
|
||||||
|
</div>
|
||||||
|
<div class="toast-body">
|
||||||
|
{{ toast.message }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
const { toasts, remove } = useToast()
|
||||||
|
|
||||||
|
function headerClass(type: string) {
|
||||||
|
const classes: Record<string, string> = {
|
||||||
|
success: 'bg-success text-white',
|
||||||
|
error: 'bg-danger text-white',
|
||||||
|
warning: 'bg-warning text-dark',
|
||||||
|
info: 'bg-primary text-white'
|
||||||
|
}
|
||||||
|
return classes[type] || classes.info
|
||||||
|
}
|
||||||
|
|
||||||
|
function iconClass(type: string) {
|
||||||
|
const icons: Record<string, string> = {
|
||||||
|
success: 'bi bi-check-circle-fill',
|
||||||
|
error: 'bi bi-x-circle-fill',
|
||||||
|
warning: 'bi bi-exclamation-triangle-fill',
|
||||||
|
info: 'bi bi-info-circle-fill'
|
||||||
|
}
|
||||||
|
return icons[type] || icons.info
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.toast {
|
||||||
|
min-width: 300px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
<div class="container-fluid">
|
<div class="container-fluid">
|
||||||
<NuxtLink class="navbar-brand" to="/">
|
<NuxtLink class="navbar-brand" to="/">
|
||||||
<i class="bi bi-clipboard-check me-2"></i>
|
<i class="bi bi-clipboard-check me-2"></i>
|
||||||
주간업무보고
|
업무관리프로그램
|
||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
|
|
||||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
|
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
|
||||||
|
|||||||
60
frontend/composables/useToast.ts
Normal file
60
frontend/composables/useToast.ts
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
/**
|
||||||
|
* Bootstrap Toast Composable
|
||||||
|
* 전역 토스트 메시지 관리
|
||||||
|
*/
|
||||||
|
interface ToastMessage {
|
||||||
|
id: number
|
||||||
|
type: 'success' | 'error' | 'warning' | 'info'
|
||||||
|
title?: string
|
||||||
|
message: string
|
||||||
|
duration?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
const toasts = ref<ToastMessage[]>([])
|
||||||
|
let toastId = 0
|
||||||
|
|
||||||
|
export function useToast() {
|
||||||
|
function show(message: string, type: ToastMessage['type'] = 'info', title?: string, duration = 3000) {
|
||||||
|
const id = ++toastId
|
||||||
|
toasts.value.push({ id, type, title, message, duration })
|
||||||
|
|
||||||
|
if (duration > 0) {
|
||||||
|
setTimeout(() => {
|
||||||
|
remove(id)
|
||||||
|
}, duration)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function success(message: string, title?: string, duration = 3000) {
|
||||||
|
show(message, 'success', title || '성공', duration)
|
||||||
|
}
|
||||||
|
|
||||||
|
function error(message: string, title?: string, duration = 5000) {
|
||||||
|
show(message, 'error', title || '오류', duration)
|
||||||
|
}
|
||||||
|
|
||||||
|
function warning(message: string, title?: string, duration = 4000) {
|
||||||
|
show(message, 'warning', title || '경고', duration)
|
||||||
|
}
|
||||||
|
|
||||||
|
function info(message: string, title?: string, duration = 3000) {
|
||||||
|
show(message, 'info', title || '알림', duration)
|
||||||
|
}
|
||||||
|
|
||||||
|
function remove(id: number) {
|
||||||
|
const idx = toasts.value.findIndex(t => t.id === id)
|
||||||
|
if (idx > -1) {
|
||||||
|
toasts.value.splice(idx, 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
toasts,
|
||||||
|
show,
|
||||||
|
success,
|
||||||
|
error,
|
||||||
|
warning,
|
||||||
|
info,
|
||||||
|
remove
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -457,6 +457,7 @@ const { fetchCurrentUser } = useAuth()
|
|||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const { getWeekInfo, getWeekDates, getLastWeekInfo, getActualCurrentWeekInfo, getMonday, changeWeek: calcChangeWeek } = useWeekCalc()
|
const { getWeekInfo, getWeekDates, getLastWeekInfo, getActualCurrentWeekInfo, getMonday, changeWeek: calcChangeWeek } = useWeekCalc()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
const toast = useToast()
|
||||||
|
|
||||||
interface TaskItem {
|
interface TaskItem {
|
||||||
projectId: number
|
projectId: number
|
||||||
@@ -656,7 +657,7 @@ async function setDefaultWeek(userId: number) {
|
|||||||
const isFutureWeek = year > currentWeek.year || (year === currentWeek.year && week > currentWeek.week)
|
const isFutureWeek = year > currentWeek.year || (year === currentWeek.year && week > currentWeek.week)
|
||||||
|
|
||||||
if (isFutureWeek) {
|
if (isFutureWeek) {
|
||||||
alert('작성할 수 없는 주차입니다.')
|
toast.warning('작성할 수 없는 주차입니다.')
|
||||||
router.replace('/report/weekly')
|
router.replace('/report/weekly')
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@@ -860,7 +861,7 @@ function formatHoursDisplay(hours: number): string {
|
|||||||
async function handleSubmit() {
|
async function handleSubmit() {
|
||||||
const validTasks = form.value.tasks.filter(t => t.description.trim())
|
const validTasks = form.value.tasks.filter(t => t.description.trim())
|
||||||
if (validTasks.length === 0) {
|
if (validTasks.length === 0) {
|
||||||
alert('최소 1개 이상의 Task를 입력해주세요.')
|
toast.warning('최소 1개 이상의 Task를 입력해주세요.')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -885,10 +886,10 @@ async function handleSubmit() {
|
|||||||
remarkDescription: form.value.remarkDescription
|
remarkDescription: form.value.remarkDescription
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
alert('주간보고가 작성되었습니다.')
|
toast.success('주간보고가 작성되었습니다.')
|
||||||
router.push('/report/weekly')
|
router.push('/report/weekly')
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
alert(e.data?.message || '저장에 실패했습니다.')
|
toast.error(e.data?.message || '저장에 실패했습니다.')
|
||||||
} finally {
|
} finally {
|
||||||
isSaving.value = false
|
isSaving.value = false
|
||||||
}
|
}
|
||||||
@@ -1000,11 +1001,11 @@ async function runAiParse() {
|
|||||||
}
|
}
|
||||||
aiStep.value = 'matching'
|
aiStep.value = 'matching'
|
||||||
} else {
|
} else {
|
||||||
alert('분석된 내용이 없습니다.')
|
toast.warning('분석된 내용이 없습니다.')
|
||||||
}
|
}
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
console.error('=== AI 분석 에러 ===', e)
|
console.error('=== AI 분석 에러 ===', e)
|
||||||
alert(e.data?.message || 'AI 분석에 실패했습니다.')
|
toast.error(e.data?.message || 'AI 분석에 실패했습니다.')
|
||||||
} finally {
|
} finally {
|
||||||
isAiParsing.value = false
|
isAiParsing.value = false
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user