시네마틱 3D 액션 광고 - [제품/브랜드 삽입], 극적인 스튜디
Wikiprompt, 무료 프롬프트 백과사전에서
시네마틱 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 도구에 붙여넣으세요.
최상의 결과를 얻으려면 자리 표시자(대괄호 또는 대문자로 표시)를 특정 요구 사항으로 사용자 지정할 수 있습니다.
토론
댓글 0개