김마드 2025. 5. 23. 16:09

> CHATGPT API 받기

https://platform.openai.com/settings/organization/data-controls/sharing

 

해당 키를 .env 파일 OPENAI_API_KEY값에 넣어두자

 

 

> SUPABASE DB 확장프로그램 사용해보자

 

경로 : DATABASE -> extensions

 

1. pg_net 활성화

2. pg_cron 활성화

 

그리고 나서, Intergrations 페이지 -> cron에 가면 해당 작업을 할 수 있다.

 

> chatgpt api 사용하기

 

*npm install openai 설치

 

먼저, 외부 api를 사용하기 위해서는(특히 유료) supabase의 api key를 별도 세팅해야 한다. 기본적으로는 위 public key를 사용해도 문제가 없다. (RLS를 하기 떄문에). 하지만 chatgpt 같은 외부 api를 호출하는 경우에는 아래 service role key를 사용 해야 한다. 

 

- supa-client.ts

export const adminClient = createClient<Database>(
  process.env.SUPABASE_URL!,
  process.env.SUPABASE_SERVICE_ROLE_KEY!
);

 

- mutations.ts

export const insertIdeas = async (
  client: SupabaseClient<Database>,
  ideas: string[]
) => {
  const { error } = await client.from("gpt_ideas").insert(
    ideas.map((idea) => ({
      idea,
    }))
  );
  if (error) {
    throw error;
  }
};

 

ideas를 배열로 받아와서 map함수로 insert해줌

 

- generate-idea-page.tsx

import OpenAI from "openai";
import { zodResponseFormat } from "openai/helpers/zod";
import { z } from "zod";
import { insertIdeas } from "../mutations";
import { adminClient } from "~/supa-client";

const openai = new OpenAI();

const IdeaSchema = z.object({
  title: z.string(),
  description: z.string({
    description: "A short description of the idea. 100 characters max.",
  }),
  problem: z.string(),
  solution: z.string(),
  category: z.enum([
    "tech",
    "business",
    "health",
    "education",
    "finance",
    "other",
  ]),
});

const ResponseSchema = z.object({
  potato: z.array(IdeaSchema),
});

export const loader = async () => {
  const completion = await openai.beta.chat.completions.parse({
    model: "gpt-4o",
    messages: [
      {
        role: "user",
        content:
          "Give the name and elevator pitch of startup ideas that can be built by small teams.",
      },
      {
        role: "user",
        content:
          "For example: 'An app that helps you find the best deals on groceries.', or 'A platform to rent a coder per hour.'",
      },
      {
        role: "user",
        content: "Give me 10 ideas.",
      },
    ],
    response_format: zodResponseFormat(ResponseSchema, "potato"),
  });
  const descriptions = completion.choices[0].message.parsed?.potato.map(
    (idea) => idea.description
  );
  if (!descriptions) {
    return Response.json(
      {
        error: "No ideas generated",
      },
      { status: 400 }
    );
  }
  await insertIdeas(adminClient, descriptions);
  return Response.json({
    ok: true,
  });
};

 

zod로 chatgpt 결과값의 포맷을 정해줄 수 있다.

 

> CRON JOB을 해보자

 

supabase -> intergrations-> cron부분에서 생성하기를 누른 다음

이 때 endpost url 부분은 localhost가 작동이 안된다. 이 때 localhost를 공개해주는 서비스를 이용해야 한다.

방법은

 

https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/downloads/

 

winget install --id Cloudflare.cloudflared 설치 (윈도우용)

 

-vite.config.ts

  server: {
    allowedHosts: true,
  },

추가

cloudflared tunnel --url http://localhost:5173/ 실행 (dev가 실행 중이어야 함)

 

위 화면을 확인 할 수 있다.
 
- generate-idea-page.tsx
import OpenAI from "openai";
import { zodResponseFormat } from "openai/helpers/zod";
import { z } from "zod";
import { insertIdeas } from "../mutations";
import { adminClient } from "~/supa-client";
import type { Route } from "./+types/generate-idea-page";

const openai = new OpenAI();

const IdeaSchema = z.object({
  title: z.string(),
  description: z.string({
    description: "A short description of the idea. 100 characters max.",
  }),
  problem: z.string(),
  solution: z.string(),
  category: z.enum([
    "tech",
    "business",
    "health",
    "education",
    "finance",
    "other",
  ]),
});

const ResponseSchema = z.object({
  potato: z.array(IdeaSchema),
});

export const action = async ({ request }: Route.ActionArgs) => {
  if (request.method !== "POST") {
    return new Response(null, { status: 404 });
  }
  const header = request.headers.get("X-POTATO");
  if (!header || header !== "X-TOMATO") {
    return new Response(null, { status: 404 });
  }
  const completion = await openai.beta.chat.completions.parse({
    model: "gpt-4o-mini",
    messages: [
      {
        role: "user",
        content:
          "Give the name and elevator pitch of startup ideas that can be built by small teams.",
      },
      {
        role: "user",
        content:
          "For example: 'An app that helps you find the best deals on groceries.', or 'A platform to rent a coder per hour.'",
      },
      {
        role: "user",
        content: "Give me 10 ideas.",
      },
    ],
    response_format: zodResponseFormat(ResponseSchema, "potato"),
  });
  const descriptions = completion.choices[0].message.parsed?.potato.map(
    (idea) => idea.description
  );
  if (!descriptions) {
    return Response.json(
      {
        error: "No ideas generated",
      },
      { status: 400 }
    );
  }
  await insertIdeas(adminClient, descriptions);
  return Response.json({
    ok: true,
  });
};
POST로 보내주었기 때문에 loader에서 action으로 바꿔준다. (loader는 get) 그리고 반드시 보안의 안정성을 위해 POST 입력값과, header값을 검증해야한다.