토론

시네마틱 3D 액션 광고 - [제품/브랜드 삽입], 극적인 스튜디

Wikiprompt, 무료 프롬프트 백과사전에서

Tugrul Erdem

2025년 12월 25일

시네마틱 3D 액션 광고 - [제품/브랜드 삽입], 극적인 스튜디 햇살 가득한 절벽 길이 키 큰 황금빛 풀 사이로 구불구불 이어진다. 아래로

프롬프트 내용저장

🌐
A) Architecture summary This architecture separates concerns to ensure cold-start safety and scalability. The Expo client communicates directly with Supabase for authentication, database reads, and realtime subscriptions. Edge Functions handle lightweight gating and job enqueueing, while a separate Worker service performs heavy AI generation and storage writes. This prevents cold-start latency from blocking the user experience and keeps Edge Functions fast and responsive. B) Migrations (SQL) ```sql -- extensions create extension if not exists "pgcrypto"; -- profiles (extends auth.users) create table public.profiles ( id uuid primary key references auth.users(id) on delete cascade, email text, credits integer not null default 0, is_paid boolean not null default false, created_at timestamptz not null default now() ); -- jobs create table public.jobs ( id uuid primary key default gen_random_uuid(), user_id uuid not null references auth.users(id) on delete cascade, status text not null default 'queued' check (status in ('queued','processing','completed','failed')), input_prompt text not null, created_at timestamptz not null default now(), updated_at timestamptz not null default now() ); -- generations create table public.generations ( id uuid primary key default gen_random_uuid(), job_id uuid not null references public.jobs(id) on delete cascade, user_id uuid not null references auth.users(id) on delete cascade, input_prompt text not null, image_url text, status text not null default 'pending' check (status in ('pending','completed','failed')), created_at timestamptz not null default now() ); -- indexes create index jobs_user_id_idx on public.jobs(user_id); create index jobs_status_idx on public.jobs(status); create index generations_user_id_idx on public.generations(user_id); create index generations_job_id_idx on public.generations(job_id); -- RLS alter table public.profiles enable row level security; alter table public.jobs enable row level security; alter table public.generations enable row level security; create policy "users can read own profile" on public.profiles for select using (auth.uid() = id); create policy "users can update own profile" on public.profiles for update using (auth.uid() = id); create policy "users can read own jobs" on public.jobs for select using (auth.uid() = user_id); create policy "users can insert own jobs" on public.jobs for insert with check (auth.uid() = user_id); create policy "users can read own generations" on public.generations for select using (auth.uid() = user_id); create policy "users can insert own generations" on public.generations for insert with check (auth.uid() = user_id); ``` C) Edge function file structure + key code blocks ``` supabase/functions/ ping/index.ts enqueue_generation/index.ts get_job_status/index.ts ``` **ping/index.ts** ```typescript import { corsHeaders } from "../_shared/cors.ts"; Deno.serve(async (req) => { if (req.method === "OPTIONS") return new Response("ok", { headers: corsHeaders }); return new Response(JSON.stringify({ status: "ok" }), { headers: { ...corsHeaders, "Content-Type": "application/json" }, }); }); ``` **enqueue_generation/index.ts** ```typescript import { createClient } from "https://esm.sh/@supabase/supabase-js@2"; import { corsHeaders } from "../_shared/cors.ts"; Deno.serve(async (req) => { if (req.method === "OPTIONS") return new Response("ok", { headers: corsHeaders }); const supabase = createClient( Deno.env.get("SUPABASE_URL")!, Deno.env.get("SUPABASE_ANON_KEY")! ); const authHeader = req.headers.get("Authorization")!; const token = authHeader.replace("Bearer ", ""); const { data: { user }, error: authError } = await supabase.auth.getUser(token); if (authError || !user) { return new Response(JSON.stringify({ error: "Unauthorized" }), { status: 401, headers: corsHeaders }); } const { prompt } = await req.json(); if (!prompt || prompt.length < 3) { return new Response(JSON.stringify({ error: "Invalid prompt" }), { status: 400, headers: corsHeaders }); } const { data: profile, error: profileError } = await supabase .from("profiles") .select("credits, is_paid") .eq("id", user.id) .single(); if (profileError || (!profile.is_paid && profile.credits <= 0)) { return new Response(JSON.stringify({ error: "Insufficient credits" }), { status: 403, headers: corsHeaders }); } const { data: job, error: jobError } = await supabase .from("jobs") .insert({ user_id: user.id, input_prompt: prompt, status: "queued" }) .select() .single(); if (jobError) { return new Response(JSON.stringify({ error: "Failed to create job" }), { status: 500, headers: corsHeaders }); } if (!profile.is_paid) { await supabase.from("profiles").update({ credits: profile.credits - 1 }).eq("id", user.id); } return new Response(JSON.stringify({ jobId: job.id }), { status: 200, headers: corsHeaders }); }); ``` **get_job_status/index.ts** ```typescript import { createClient } from "https://esm.sh/@supabase/supabase-js@2"; import { corsHeaders } from "../_shared/cors.ts"; Deno.serve(async (req) => { if (req.method === "OPTIONS") return new Response("ok", { headers: corsHeaders }); const supabase = createClient( Deno.env.get("SUPABASE_URL")!, Deno.env.get("SUPABASE_ANON_KEY")! ); const authHeader = req.headers.get("Authorization")!; const token = authHeader.replace("Bearer ", ""); const { data: { user }, error: authError } = await supabase.auth.getUser(token); if (authError || !user) { return new Response(JSON.stringify({ error: "Unauthorized" }), { status: 401, headers: corsHeaders }); } const url = new URL(req.url); const jobId = url.searchParams.get("jobId"); if (!jobId) { return new Response(JSON.stringify({ error: "Missing jobId" }), { status: 400, headers: corsHeaders }); } const { data: job, error } = await supabase .from("jobs") .select("id, status, updated_at") .eq("id", jobId) .eq("user_id", user.id) .single(); if (error) { return new Response(JSON.stringify({ error: "Job not found" }), { status: 404, headers: corsHeaders }); } return new Response(JSON.stringify(job), { status: 200, headers: corsHeaders }); }); ``` D) Expo integration notes + key code blocks - On app start, fire a non-blocking ping to warm the Edge Function runtime. Do not await it. - Generate button: optimistic UI with placeholder, then enqueue job via Edge Function. - Subscribe to Realtime on the jobs table for the user's jobs. Fallback to polling every 5 seconds. - When job completes, fetch the generation and replace the placeholder in the gallery. ```typescript // appStart.ts export function warmPing() { fetch(`${SUPABASE_URL}/functions/v1/ping`, { method: "HEAD" }).catch(() => {}); } // generate.ts export async function generate(prompt: string) { const token = (await supabase.auth.getSession()).data.session?.access_token; const res = await fetch(`${SUPABASE_URL}/functions/v1/enqueue_generation`, { method: "POST", headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}` }, body: JSON.stringify({ prompt }), }); const { jobId } = await res.json(); return jobId; } // subscribe.ts export function subscribeToJob(jobId: string, onUpdate: (job: any) => void) { const channel = supabase .channel(`job-${jobId}`) .on("postgres_changes", { event: "UPDATE", schema: "public", table: "jobs", filter: `id=eq.${jobId}` }, (payload) => { onUpdate(payload.new); }) .subscribe(); return channel; } // pollingFallback.ts export function pollJob(jobId: string, onUpdate: (job: any) => void) { const token = (await supabase.auth.getSession()).data.session?.access_token; const interval = setInterval(async () => { const res = await fetch(`${SUPABASE_URL}/functions/v1/get_job_status?jobId=${jobId}`, { headers: { Authorization: `Bearer ${token}` }, }); const job = await res.json(); onUpdate(job); if (job.status === "completed" || job.status === "failed") clearInterval(interval); }, 5000); } ``` E) Worker outline + pseudo-code The Worker is a separate service (Node.js, Python, etc.) that polls the jobs table for queued jobs, processes them, and updates the database. **Responsibilities:** - Poll for queued jobs (every 5 seconds) - Claim job (update status to processing) - Run AI generation - Upload result to Supabase Storage - Update job status and insert generation record - Retry with exponential backoff (max 3 attempts) - Idempotency: check if generation already exists for job before inserting **Pseudo-code:** ``` loop: jobs = SELECT * FROM jobs WHERE status = 'queued' LIMIT 1 if no jobs: sleep(5s); continue UPDATE jobs SET status = 'processing' WHERE id = jobs.id AND status = 'queued' if no rows updated: continue // another worker claimed it try: image = ai_generate(jobs.input_prompt) image_url = upload_to_storage(image, `generations/${jobs.user_id}/${jobs.id}.png`) UPDATE jobs SET status = 'completed', updated_at = now() WHERE id = jobs.id INSERT INTO generations (job_id, user_id, input_prompt, image_url, status) VALUES (jobs.id, jobs.user_id, jobs.input_prompt, image_url, 'completed') ON CONFLICT (job_id) DO NOTHING // idempotency catch error: if attempts < 3: sleep(2^attempts * 5s) retry else: UPDATE jobs SET status = 'failed', updated_at = now() WHERE id = jobs.id INSERT INTO generations (job_id, user_id, input_prompt, status) VALUES (jobs.id, jobs.user_id, jobs.input_prompt, 'failed') ON CONFLICT (job_id) DO NOTHING ```

전체 프롬프트를 보려면 로그인하세요

Continue with:

By logging in, you agree to our Terms of Use and Privacy Policy

사용법

이 프롬프트는 coding와 함께 사용하도록 설계되었습니다. 위의 프롬프트 내용을 복사하여 원하는 AI 도구에 붙여넣으세요.

최상의 결과를 얻으려면 자리 표시자(대괄호 또는 대문자로 표시)를 특정 요구 사항으로 사용자 지정할 수 있습니다.

참고 자료

분류:coding| prompts.chat| expo| supabase

토론