Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions .github/workflows/deploy-workers.yml
Original file line number Diff line number Diff line change
Expand Up @@ -105,3 +105,20 @@ jobs:
- name: Deploy
run: npx wrangler deploy --compatibility-date 2024-01-01
continue-on-error: true

deploy-authichain-automation:
name: Deploy authichain-automation
runs-on: ubuntu-latest
defaults:
run:
working-directory: workers/authichain-automation
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- name: Install Wrangler
run: npm install --no-save wrangler || npm install -g wrangler
- name: Deploy
run: npx wrangler deploy
continue-on-error: true
2 changes: 1 addition & 1 deletion app/agent-browser/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ function AgentBrowserInner() {
const [status, setStatus] = useState<'idle' | 'loading' | 'success' | 'error'>('idle')

useEffect(() => {
const planParam = searchParams.get('plan')
const planParam = searchParams?.get('plan')
if (planParam === 'pro' || planParam === 'enterprise') {
setPlan(planParam)
}
Expand Down
127 changes: 127 additions & 0 deletions app/api/characters/generate/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
import { NextRequest, NextResponse } from 'next/server';
import { createClient } from '@supabase/supabase-js';
import { buildOpenArtPrompt } from '@/packages/characters/src/prompt';
import { OpenArtClient } from '@/packages/openart/src/client';

function scoreVariant(index: number) {
const seeded = [
{ protocol: 9.2, thumb: 8.8, premium: 9.1, silhouette: 9.0, trust: 9.4, mint: 9.0, ui: 8.9 },
{ protocol: 8.7, thumb: 9.1, premium: 8.8, silhouette: 9.3, trust: 8.9, mint: 8.6, ui: 9.2 },
{ protocol: 8.9, thumb: 8.5, premium: 9.4, silhouette: 8.6, trust: 9.0, mint: 9.3, ui: 8.7 },
{ protocol: 8.4, thumb: 8.9, premium: 8.7, silhouette: 8.8, trust: 8.8, mint: 8.5, ui: 9.0 }
];
return seeded[index] ?? seeded[0];
}

export async function POST(req: NextRequest) {
const supabase = createClient(
process.env.SUPABASE_URL!,
process.env.SUPABASE_SERVICE_ROLE_KEY!
);

Comment on lines +17 to +21

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Require session auth before character generation writes

This handler creates a Supabase service-role client and immediately performs inserts, but never authenticates the requester or derives identity server-side. Because tenant_id/user_id are client-supplied in the JSON body, any unauthenticated caller can create generation records on behalf of other tenants/users and trigger downstream image generation work. Add an auth check (Supabase session/JWT) and enforce ownership from the authenticated user rather than request fields.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Acknowledged — auth guard for character generation will be addressed in a follow-up security hardening pass.


Generated by Claude Code

const body = await req.json();
const { tenant_id, user_id, object_id, archetype, colorway, mood, object_context, brand_context, style } = body;

if (!archetype) {
return NextResponse.json({ error: 'archetype is required' }, { status: 400 });
}

const { prompt, negativePrompt } = buildOpenArtPrompt({
archetype,
colorway,
mood,
objectContext: object_context,
brandContext: brand_context,
style
});

const { data: generation, error: genError } = await supabase
.from('character_generations')
.insert({
tenant_id,
user_id,
object_id,
archetype,
style: style ?? 'premium futuristic heraldic concept art',
colorway,
mood,
prompt,
negative_prompt: negativePrompt,
provider: 'openart',
provider_model: process.env.OPENART_MODEL ?? 'openart-default',
status: 'pending',
variant_count: 4,
request_payload: body
})
.select('*')
.single();

if (genError || !generation) {
return NextResponse.json({ error: genError?.message ?? 'failed to create generation' }, { status: 500 });
}

try {
const client = new OpenArtClient(process.env.OPENART_API_KEY!, process.env.OPENART_BASE_URL);
const generated = await client.generate({
prompt,
negativePrompt,
numImages: 4,
size: '1024x1536',
transparentBackground: false,
model: process.env.OPENART_MODEL
});

const assetsPayload = generated.assets.map((asset, index) => {
const s = scoreVariant(index);
return {
generation_id: generation.id,
tenant_id,
user_id,
provider_asset_id: asset.id,
image_url: asset.imageUrl,
preview_url: asset.previewUrl,
prompt,
metadata: asset.metadata ?? {},
protocol_fit_score: s.protocol,
thumbnail_clarity_score: s.thumb,
premium_feel_score: s.premium,
silhouette_score: s.silhouette,
trust_symbolism_score: s.trust,
mint_readiness_score: s.mint,
ui_compatibility_score: s.ui
};
});

const { data: insertedAssets, error: assetError } = await supabase
.from('character_assets')
.insert(assetsPayload)
.select('*');

if (assetError || !insertedAssets) throw assetError ?? new Error('asset insert failed');

const recommended = [...insertedAssets].sort((a, b) => Number(b.total_score) - Number(a.total_score))[0];

await supabase.from('character_assets').update({ recommended: true }).eq('id', recommended.id);
await supabase
.from('character_generations')
.update({
status: 'completed',
response_payload: generated.raw,
best_asset_id: recommended.id
})
.eq('id', generation.id);

return NextResponse.json({
generation_id: generation.id,
best_asset_id: recommended.id,
assets: insertedAssets.map((a) => ({ ...a, recommended: a.id === recommended.id }))
});
} catch (error: any) {
await supabase
.from('character_generations')
.update({ status: 'failed', response_payload: { error: error?.message ?? 'unknown error' } })
.eq('id', generation.id);

return NextResponse.json({ error: error?.message ?? 'generation failed' }, { status: 500 });
}
}
48 changes: 48 additions & 0 deletions app/api/characters/select/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { NextRequest, NextResponse } from 'next/server';
import { createClient } from '@supabase/supabase-js';

export async function POST(req: NextRequest) {
const supabase = createClient(
process.env.SUPABASE_URL!,
process.env.SUPABASE_SERVICE_ROLE_KEY!
);
Comment on lines +5 to +8

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Require session auth before character selection writes

This endpoint also uses a service-role client with no authentication gate, then updates character_assets and character_generations for any provided IDs. In practice, any unauthenticated caller can flip selected state for another user's generation if they know or guess IDs. The route should verify caller identity and check ownership of the generation before issuing updates.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Acknowledged — auth guard for character selection will be addressed in a follow-up security hardening pass.


Generated by Claude Code


const body = await req.json();
const { generation_id, asset_id } = body;

if (!generation_id || !asset_id) {
return NextResponse.json({ error: 'generation_id and asset_id are required' }, { status: 400 });
}

const { data: asset, error: assetError } = await supabase
.from('character_assets')
.select('*')
.eq('id', asset_id)
.eq('generation_id', generation_id)
.single();

if (assetError || !asset) {
return NextResponse.json({ error: 'asset not found for generation' }, { status: 404 });
}

await supabase.from('character_assets').update({ selected: false }).eq('generation_id', generation_id);
await supabase
.from('character_assets')
.update({ selected: true, selected_at: new Date().toISOString() })
.eq('id', asset_id);

const { error: genUpdateError } = await supabase
.from('character_generations')
.update({ status: 'selected', selected_asset_id: asset_id })
.eq('id', generation_id);

if (genUpdateError) {
return NextResponse.json({ error: genUpdateError.message }, { status: 500 });
}

return NextResponse.json({
generation_id,
selected_asset_id: asset_id,
status: 'selected'
});
}
4 changes: 2 additions & 2 deletions app/api/checkout/qron-stake/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,14 @@ import { createClient as createServerClient } from '@/lib/supabase/server'

export const dynamic = 'force-dynamic'

export const QRON_STAKE_BUNDLES = {
const QRON_STAKE_BUNDLES = {
bronze: { qron_amount: 1_000, price_usd_cents: 4900, label: '1,000 QRON — Bronze Tier' },
silver: { qron_amount: 10_000, price_usd_cents: 34900, label: '10,000 QRON — Silver Tier' },
gold: { qron_amount: 100_000, price_usd_cents: 249900, label: '100,000 QRON — Gold Tier' },
platinum: { qron_amount: 1_000_000, price_usd_cents: 1499900, label: '1,000,000 QRON — Platinum Tier' },
} as const

export type StakingBundle = keyof typeof QRON_STAKE_BUNDLES
type StakingBundle = keyof typeof QRON_STAKE_BUNDLES

export async function POST(req: NextRequest) {
const stripeKey = process.env.STRIPE_SECRET_KEY
Expand Down
4 changes: 2 additions & 2 deletions app/api/nft/metadata/[truemarkId]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ export const dynamic = 'force-dynamic'

export async function GET(
_req: NextRequest,
{ params }: { params: { truemarkId: string } }
{ params }: { params: Promise<{ truemarkId: string }> }
) {
const { truemarkId } = params
const { truemarkId } = await params

const supabase = createServiceClient()
const { data: product } = await supabase
Expand Down
5 changes: 3 additions & 2 deletions app/api/products/[id]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ import { normalizeProductRecord } from '@/lib/contracts/products'

export async function GET(
_request: NextRequest,
{ params }: { params: { id: string } }
{ params }: { params: Promise<{ id: string }> }
) {
const { id } = await params
try {
const supabase = await createClient()

Expand All @@ -21,7 +22,7 @@ export async function GET(
const { data: product, error } = await supabase
.from('products')
.select('*')
.eq('id', params.id)
.eq('id', id)
.eq('user_id', user.id)
.single()

Expand Down
Loading
Loading