import { avatarUrl } from '$lib/avatar'; import { parseLoadoutDraft } from '$lib/builder/loadout'; import { summarizeLoadout } from '$lib/builder/loadout-summary'; import { getDatabase } from '$lib/server/database'; import { loadSharedBuildSummaryCatalog } from '$lib/server/shared-build-summary'; export type SharedBuildAccessPolicy = { kind: 'viewer-page'; viewerId: string | null } | { kind: 'public-preview' }; type BuildVisibility = 'public' | 'unlisted' | 'private'; export async function loadSharedBuildProjection( slug: string, access: SharedBuildAccessPolicy, ) { const database = await getDatabase(); const build = await ( await database.prepare(` SELECT builds.*,COALESCE(user_profiles.display_name,users.username,'Deleted Agent') AS author, user_profiles.public_slug, COALESCE(user_profiles.is_public,1) AS profile_is_public,deleted_accounts.user_id AS deleted_user_id, user_avatars.source AS avatar_source,user_avatars.value AS avatar_value, user_avatars.updated_at AS avatar_updated_at FROM builds JOIN users ON users.id=builds.owner_id LEFT JOIN user_profiles ON user_profiles.user_id=users.id LEFT JOIN deleted_accounts ON deleted_accounts.user_id=users.id LEFT JOIN user_avatars ON user_avatars.user_id=users.id WHERE builds.slug=? `) ).get(slug); if (!build) return null; const ownerId = String(build.owner_id); const visibility = String(build.visibility) as BuildVisibility; const viewerId = access.kind === 'viewer-page' ? access.viewerId : null; const accessible = access.kind === 'public-preview' ? visibility === 'public' || visibility === 'unlisted' : visibility === 'public' || visibility === 'unlisted' || (visibility === 'private' && ownerId === viewerId); if (!accessible) return null; let loadout; try { loadout = parseLoadoutDraft(JSON.parse(String(build.loadout_json))); } catch { return null; } if (!loadout) return null; const [gear, weapons, chips, protocols, brands, summaryCatalog] = await Promise.all([ ( await database.prepare( 'SELECT id,name,slot,tier,base_armor_min FROM gear', ) ).all(), ( await database.prepare( 'SELECT id,name,category,is_exotic,rpm,magazine,reload_speed,damage_type,exotic_talent_id FROM weapons', ) ).all(), (await database.prepare('SELECT id,name FROM skill_chip_sets')).all(), ( await database.prepare('SELECT id,name,quality FROM os_protocols') ).all(), (await database.prepare('SELECT id,name FROM brand_sets')).all(), loadSharedBuildSummaryCatalog(database), ]); const summary = summarizeLoadout(loadout, summaryCatalog); return { build: { id: String(build.id), title: String(build.title), slug: String(build.slug), visibility, author: String(build.author), ownerId, authorPublicSlug: build.public_slug ? String(build.public_slug) : null, profileAvailable: Boolean(build.profile_is_public) && !build.deleted_user_id, authorAvatarUrl: avatarUrl( build.avatar_source, build.avatar_value, ownerId, build.avatar_updated_at, ), owner: ownerId === viewerId, updatedAt: String(build.updated_at), }, loadout, gear, weapons, chips, protocols, brands, summary, attributes: summaryCatalog.attributes, talents: summaryCatalog.talents, }; }