공개 문서

app/api/subscription/route.ts

아래는 app/api/subscription/route.ts 와 동일한 원문입니다. Markdown과 HTML 변환 결과를 각각 복사할 수 있습니다.

공개 문서 원문 (Markdown)

import { NextRequest, NextResponse } from "next/server";
import { withAuth } from "@/lib/api-auth";
import { isMissingRelationOrSchemaCacheError } from "@/lib/supabase-schema-error";

export const dynamic = "force-dynamic";

/**
 * 로그인 사용자의 `public.subscriptions` 행 조회.
 *
 * - 행이 없으면 응답만 `plan: "free"`, `status: "none"` (회원가입 트리거로 subscriptions 행을 넣지 않는 마이그레이션과 정합).
 * - 행이 있으면 웹훅 동기화 결과(plan/status/current_period_end 등)를 그대로 반영.
 */
export async function GET(request: NextRequest) {
  const auth = await withAuth(request);
  if (auth.unauth) return auth.unauth;
  const { user, supabase } = auth;

  const { data, error } = await supabase
    .from("subscriptions")
    .select("plan, status, current_period_end, polar_subscription_id")
    .eq("user_id", user.id)
    .maybeSingle();

  if (error) {
    console.error("[api/subscription]", error);
    // 운영 중 마이그레이션 누락·schema cache 미스 시 대시보드 전체가 깨지지 않도록 안전 폴백.
    if (isMissingRelationOrSchemaCacheError(error)) {
      return NextResponse.json({
        plan: "free",
        status: "none",
        current_period_end: null,
        polar_subscription_id: null,
        warning: "subscriptions 테이블이 없어 free 플랜으로 폴백했습니다.",
      });
    }

    return NextResponse.json(
      { error: error.message, hint: "subscriptions 테이블(005 마이그레이션) 적용 여부 확인" },
      { status: 500 }
    );
  }

  if (!data) {
    return NextResponse.json({
      plan: "free",
      status: "none",
      current_period_end: null,
      polar_subscription_id: null,
    });
  }

  return NextResponse.json({
    plan: data.plan,
    status: data.status,
    current_period_end: data.current_period_end,
    polar_subscription_id: data.polar_subscription_id,
  });
}

공개 문서 변환 코드 (HTML)

<pre class="public-doc-text-as-html-wrap"><code>import { NextRequest, NextResponse } from &quot;next/server&quot;;
import { withAuth } from &quot;@/lib/api-auth&quot;;
import { isMissingRelationOrSchemaCacheError } from &quot;@/lib/supabase-schema-error&quot;;

export const dynamic = &quot;force-dynamic&quot;;

/**
 * 로그인 사용자의 `public.subscriptions` 행 조회.
 *
 * - 행이 없으면 응답만 `plan: &quot;free&quot;`, `status: &quot;none&quot;` (회원가입 트리거로 subscriptions 행을 넣지 않는 마이그레이션과 정합).
 * - 행이 있으면 웹훅 동기화 결과(plan/status/current_period_end 등)를 그대로 반영.
 */
export async function GET(request: NextRequest) {
  const auth = await withAuth(request);
  if (auth.unauth) return auth.unauth;
  const { user, supabase } = auth;

  const { data, error } = await supabase
    .from(&quot;subscriptions&quot;)
    .select(&quot;plan, status, current_period_end, polar_subscription_id&quot;)
    .eq(&quot;user_id&quot;, user.id)
    .maybeSingle();

  if (error) {
    console.error(&quot;[api/subscription]&quot;, error);
    // 운영 중 마이그레이션 누락·schema cache 미스 시 대시보드 전체가 깨지지 않도록 안전 폴백.
    if (isMissingRelationOrSchemaCacheError(error)) {
      return NextResponse.json({
        plan: &quot;free&quot;,
        status: &quot;none&quot;,
        current_period_end: null,
        polar_subscription_id: null,
        warning: &quot;subscriptions 테이블이 없어 free 플랜으로 폴백했습니다.&quot;,
      });
    }

    return NextResponse.json(
      { error: error.message, hint: &quot;subscriptions 테이블(005 마이그레이션) 적용 여부 확인&quot; },
      { status: 500 }
    );
  }

  if (!data) {
    return NextResponse.json({
      plan: &quot;free&quot;,
      status: &quot;none&quot;,
      current_period_end: null,
      polar_subscription_id: null,
    });
  }

  return NextResponse.json({
    plan: data.plan,
    status: data.status,
    current_period_end: data.current_period_end,
    polar_subscription_id: data.polar_subscription_id,
  });
}
</code></pre>