diff options
Diffstat (limited to 'lib/approval/CRONJOB_CONTEXT_FIX.md')
| -rw-r--r-- | lib/approval/CRONJOB_CONTEXT_FIX.md | 278 |
1 files changed, 278 insertions, 0 deletions
diff --git a/lib/approval/CRONJOB_CONTEXT_FIX.md b/lib/approval/CRONJOB_CONTEXT_FIX.md new file mode 100644 index 00000000..a8169a5e --- /dev/null +++ b/lib/approval/CRONJOB_CONTEXT_FIX.md @@ -0,0 +1,278 @@ +# Cronjob Request Context λ¬Έμ ν΄κ²° + +## π λ¬Έμ μν© + +κ²°μ¬ μλ£ ν `pendingActions`μμ νΈλ€λ¬λ₯Ό νΈμΆν λ, **node-cronμ cronjobμ΄ ν¨μλ₯Ό νΈμΆνλ―λ‘ Request Contextκ° μμ΄** λ€μ λ¬Έμ κ° λ°μνμ΅λλ€: + +``` +β Error: `headers` was called outside a request scope. +β Error: Invariant: static generation store missing in revalidateTag +``` + +### λ°μ μμΉ + +- `lib/vendors/service.ts` - `approveVendors()`, `rejectVendors()` +- Next.jsμ `headers()`, `revalidateTag()` λ±μ API μ¬μ© + +### κ·Όλ³Έ μμΈ + +```typescript +Cronjob (node-cron) + β (Request Context μμ) +ApprovalExecutionSaga + β +Handler (μ: approveVendorWithMDGInternal) + β +approveVendors() + β headers() β - AsyncLocalStorageμ μ κ·Ό λΆκ° + β revalidateTag() β - Static generation store μμ +``` + +## β
ν΄κ²° λ°©λ² + +### 1. API κΈ°λ° Revalidation μ¬μ© + +Request Contextκ° μλ νκ²½μμλ **λ΄λΆ APIλ₯Ό νΈμΆ**νμ¬ μΊμλ₯Ό 무ν¨νν©λλ€. + +```typescript +// β Before: μ§μ νΈμΆ (Request Context νμ) +revalidateTag("vendors"); +revalidateTag("users"); + +// β
After: API νΈμΆ (Request Context λΆνμ) +const { revalidateApprovalRelatedCaches } = await import('@/lib/revalidation-utils'); +await revalidateApprovalRelatedCaches(); +``` + +### 2. νκ²½λ³μλ‘ BaseURL κ΅¬μ± + +`headers()`λ₯Ό μ¬μ©νλ λμ νκ²½λ³μλ₯Ό μ¬μ©ν©λλ€. + +```typescript +// β Before: headers()λ‘ λμ κ΅¬μ± +const headersList = await headers(); +const host = headersList.get('host') || 'localhost:3000'; +const protocol = headersList.get('x-forwarded-proto') || 'http'; +const baseUrl = `${protocol}://${host}`; + +// β
After: νκ²½λ³μ μ¬μ© +const baseUrl = process.env.NEXT_PUBLIC_BASE_URL || 'http://localhost:3000'; +``` + +## π λ³κ²½λ νμΌ + +### 1. μλ‘ μΆκ°λ νμΌ + +#### `lib/revalidation-utils.ts` +Request Contextκ° μλ νκ²½μμ μΊμλ₯Ό 무ν¨ννκΈ° μν μ νΈλ¦¬ν° ν¨μλ€: + +```typescript +// λ²μ© revalidation +await revalidateViaCronJob({ tags: ['vendors', 'users'] }); + +// νΉμ λλ©μΈλ³ revalidation +await revalidateVendorCaches(); +await revalidateUserCaches(); +await revalidateApprovalRelatedCaches(); +``` + +### 2. μμ λ νμΌ + +#### `lib/vendors/service.ts` + +**approveVendors() ν¨μ:** +- β
`headers()` μ κ±° β `process.env.NEXT_PUBLIC_BASE_URL` μ¬μ© +- β
`revalidateTag()` μ κ±° β `revalidateApprovalRelatedCaches()` μ¬μ© + +**rejectVendors() ν¨μ:** +- β
`headers()` μ κ±° β `process.env.NEXT_PUBLIC_BASE_URL` μ¬μ© +- β
`revalidateTag()` μ κ±° β `revalidateVendorCaches()`, `revalidateUserCaches()` μ¬μ© + +## π§ μ€μ νμ + +### νκ²½λ³μ μ€μ + +`.env` νμΌμ λ€μ νκ²½λ³μλ₯Ό μΆκ°νμΈμ: + +```bash +# μ ν리μΌμ΄μ
Base URL (μ΄λ©μΌ λ§ν¬ λ±μ μ¬μ©) +NEXT_PUBLIC_BASE_URL=https://your-domain.com + +# Revalidation API 보μ (μ νμ¬ν) +REVALIDATION_SECRET=your-secret-key +``` + +**κ°λ° νκ²½:** +```bash +NEXT_PUBLIC_BASE_URL=http://localhost:3000 +``` + +**νλ‘λμ
νκ²½:** +```bash +NEXT_PUBLIC_BASE_URL=https://evcp.your-company.com +``` + +## π― μλ λ°©μ + +### 1. Cronjobμμ νΈλ€λ¬ μ€ν + +```typescript +// lib/approval/approval-polling-service.ts +const saga = new ApprovalExecutionSaga(apInfId); +await saga.execute(); // β Request Context μμ +``` + +### 2. νΈλ€λ¬μμ Service ν¨μ νΈμΆ + +```typescript +// lib/vendors/approval-handlers.ts +export async function approveVendorWithMDGInternal(payload) { + // MDG μ μ‘... + + const approveResult = await approveVendors({ + ids: payload.vendorIds, + userId: payload.userId, + }); // β Request Context μλ μνλ‘ νΈμΆλ¨ +} +``` + +### 3. Serviceμμ API κΈ°λ° Revalidation + +```typescript +// lib/vendors/service.ts +export async function approveVendors(input) { + await db.transaction(async (tx) => { + // 1. λ²€λ μΉμΈ μ²λ¦¬ + // 2. μ΄λ©μΌ λ°μ‘ (β
νκ²½λ³μ μ¬μ©) + const baseUrl = process.env.NEXT_PUBLIC_BASE_URL; + const loginUrl = `${baseUrl}/${userLang}/login`; + }); + + // 3. μΊμ 무ν¨ν (β
API νΈμΆ μ¬μ©) + const { revalidateApprovalRelatedCaches } = await import('@/lib/revalidation-utils'); + await revalidateApprovalRelatedCaches(); +} +``` + +### 4. Revalidation Utilsμμ λ΄λΆ API νΈμΆ + +```typescript +// lib/revalidation-utils.ts +export async function revalidateViaCronJob(options) { + const baseUrl = process.env.NEXT_PUBLIC_BASE_URL || 'http://localhost:3000'; + + const response = await fetch(`${baseUrl}/api/revalidate/approval`, { + method: 'POST', + body: JSON.stringify({ + tags: options.tags, + secret: process.env.REVALIDATION_SECRET, + }), + }); + + return response.json(); +} +``` + +### 5. API Routeμμ μ€μ Revalidation μ€ν + +```typescript +// app/api/revalidate/approval/route.ts +export async function POST(request: NextRequest) { + // β
μ¬κΈ°λ HTTP Request Contextκ° μμ! + const { tags } = await request.json(); + + for (const tag of tags) { + revalidateTag(tag); // β
Request Contextκ° μμΌλ―λ‘ μλν¨ + } + + return Response.json({ success: true }); +} +``` + +## π νλ¦λ + +``` +βββββββββββββββββββββββββββββββββββββββββββββββββββββββ +β Cronjob (node-cron) - 1λΆλ§λ€ β +β Request Context: β μμ β +βββββββββββββββββββββββββββββββββββββββββββββββββββββββ + β +βββββββββββββββββββββββββββββββββββββββββββββββββββββββ +β ApprovalExecutionSaga β +β - Knox κ²°μ¬ μν νμΈ β +β - μΉμΈλ κ²½μ° Handler μ€ν β +βββββββββββββββββββββββββββββββββββββββββββββββββββββββ + β +βββββββββββββββββββββββββββββββββββββββββββββββββββββββ +β approveVendorWithMDGInternal() β +β - MDG μ μ‘ β +β - approveVendors() νΈμΆ β +βββββββββββββββββββββββββββββββββββββββββββββββββββββββ + β +βββββββββββββββββββββββββββββββββββββββββββββββββββββββ +β approveVendors() - lib/vendors/service.ts β +β β
baseUrl = process.env.NEXT_PUBLIC_BASE_URL β +β β
await revalidateViaCronJob({ tags: [...] }) β +βββββββββββββββββββββββββββββββββββββββββββββββββββββββ + β +βββββββββββββββββββββββββββββββββββββββββββββββββββββββ +β revalidateViaCronJob() - lib/revalidation-utils.ts β +β β
fetch('/api/revalidate/approval', ...) β +βββββββββββββββββββββββββββββββββββββββββββββββββββββββ + β +βββββββββββββββββββββββββββββββββββββββββββββββββββββββ +β POST /api/revalidate/approval β +β Request Context: β
μμ! β +β β
revalidateTag() μ¬μ© κ°λ₯ β +βββββββββββββββββββββββββββββββββββββββββββββββββββββββ +``` + +## π¨ μ£Όμμ¬ν + +### 1. νκ²½λ³μ μ€μ νμ + +`NEXT_PUBLIC_BASE_URL`μ΄ μ€μ λμ§ μμΌλ©΄ `http://localhost:3000`μ΄ κΈ°λ³Έκ°μΌλ‘ μ¬μ©λ©λλ€. νλ‘λμ
μμλ λ°λμ μ€μ νμΈμ. + +### 2. μ΄λ©μΌ λ§ν¬ μ νμ± + +μ΄λ©μΌμ ν¬ν¨λλ λ§ν¬κ° μ¬λ°λ₯Έ λλ©μΈμ κ°λ¦¬ν€λμ§ νμΈνμΈμ: + +```typescript +// λ‘κ·ΈμΈ λ§ν¬ μμ +https://evcp.your-company.com/ko/login + +// λΉλ°λ²νΈ μ¬μ€μ λ§ν¬ μμ +https://evcp.your-company.com/ko/auth/reset-password?token=xxxxx +``` + +### 3. Revalidation API 보μ + +`REVALIDATION_SECRET`μ μ€μ νμ¬ μΈλΆμμ 무λ¨μΌλ‘ μΊμλ₯Ό 무ν¨ννλ κ²μ λ°©μ§ν μ μμ΅λλ€ (μ νμ¬ν). + +### 4. λ€λ₯Έ Service ν¨μμλ μ μ© + +`lib/vendors/service.ts`μ λ€λ₯Έ ν¨μλ€λ λμΌν ν¨ν΄μ μ¬μ©ν΄μΌ ν μ μμ΅λλ€: + +- `requestPQVendors()` - line 3210-3215μ `revalidateTag()` μ¬μ© +- κΈ°ν `headers()` λλ `revalidateTag()`λ₯Ό μ¬μ©νλ ν¨μλ€ + +νμμ λμΌν ν¨ν΄μΌλ‘ μμ νμΈμ. + +## π κ²°κ³Ό + +μ΄μ cronjobμμ νΈλ€λ¬κ° μ€νλμ΄λ λ€μκ³Ό κ°μ΄ μ μ μλν©λλ€: + +```bash +β
[ExecutionSaga] Step 5: Executing action +β
[Vendor Approval Handler] MDG μ μ‘ μ±κ³΅ +β
[Vendor Approval Handler] λ²€λ μΉμΈ μλ£ +β
[Revalidation] Cache invalidated: vendors, users, roles +β
[ExecutionSaga] β Action executed +``` + +## π μ°Έκ³ μλ£ + +- [Next.js Dynamic APIs](https://nextjs.org/docs/messages/next-dynamic-api-wrong-context) +- [Next.js Revalidation](https://nextjs.org/docs/app/building-your-application/data-fetching/revalidating) +- [AsyncLocalStorage](https://nodejs.org/api/async_context.html#class-asynclocalstorage) + |
