import { auth } from './auth.js'; import { recordCache } from './record-cache.js'; import { parseTextToFacets } from '../lib/richtext.js'; import { grainApi } from './grain-api.js'; class MutationsService { async createFavorite(galleryUri) { const client = auth.getClient(); const result = await client.mutate(` mutation CreateFavorite($input: SocialGrainFavoriteInput!) { createSocialGrainFavorite(input: $input) { uri } } `, { input: { subject: galleryUri, createdAt: new Date().toISOString() } }); return result.createSocialGrainFavorite.uri; } async deleteFavorite(favoriteUri) { const client = auth.getClient(); const rkey = favoriteUri.split('/').pop(); await client.mutate(` mutation DeleteFavorite($rkey: String!) { deleteSocialGrainFavorite(rkey: $rkey) { uri } } `, { rkey }); } async toggleFavorite(galleryUri, viewerHasFavorited, viewerFavoriteUri, currentCount) { let newFavoriteUri = null; let newCount = currentCount; if (viewerHasFavorited) { await this.deleteFavorite(viewerFavoriteUri); newCount = Math.max(0, currentCount - 1); } else { newFavoriteUri = await this.createFavorite(galleryUri); newCount = currentCount + 1; } const update = { viewerHasFavorited: !viewerHasFavorited, viewerFavoriteUri: newFavoriteUri, favoriteCount: newCount }; recordCache.set(galleryUri, update); return update; } async createFollow(did) { const client = auth.getClient(); const result = await client.mutate(` mutation CreateFollow($input: SocialGrainGraphFollowInput!) { createSocialGrainGraphFollow(input: $input) { uri } } `, { input: { subject: did, createdAt: new Date().toISOString() } }); return result.createSocialGrainGraphFollow.uri; } async deleteFollow(followUri) { const client = auth.getClient(); const rkey = followUri.split('/').pop(); await client.mutate(` mutation DeleteFollow($rkey: String!) { deleteSocialGrainGraphFollow(rkey: $rkey) { uri } } `, { rkey }); } async toggleFollow(handle, did, viewerIsFollowing, viewerFollowUri, currentCount) { let newFollowUri = null; let newCount = currentCount; if (viewerIsFollowing) { await this.deleteFollow(viewerFollowUri); newCount = Math.max(0, currentCount - 1); } else { newFollowUri = await this.createFollow(did); newCount = currentCount + 1; } const update = { viewerIsFollowing: !viewerIsFollowing, viewerFollowUri: newFollowUri, followerCount: newCount }; recordCache.set(`profile:${handle}`, update); return update; } async createComment(galleryUri, text, replyToUri = null, focusUri = null) { const client = auth.getClient(); // Parse text for facets with handle resolution const resolveHandle = async (handle) => grainApi.resolveHandle(handle); const { facets } = await parseTextToFacets(text, resolveHandle); const input = { subject: galleryUri, text, createdAt: new Date().toISOString() }; // Only include facets if we found any if (facets && facets.length > 0) { input.facets = facets; } if (replyToUri) { input.replyTo = replyToUri; } if (focusUri) { input.focus = focusUri; } const result = await client.mutate(` mutation CreateComment($input: SocialGrainCommentInput!) { createSocialGrainComment(input: $input) { uri } } `, { input }); return result.createSocialGrainComment.uri; } async deleteComment(commentUri) { const client = auth.getClient(); const rkey = commentUri.split('/').pop(); await client.mutate(` mutation DeleteComment($rkey: String!) { deleteSocialGrainComment(rkey: $rkey) { uri } } `, { rkey }); } async uploadBlob(base64Data, mimeType = 'image/jpeg') { const client = auth.getClient(); const result = await client.mutate(` mutation UploadBlob($data: String!, $mimeType: String!) { uploadBlob(data: $data, mimeType: $mimeType) { ref mimeType size } } `, { data: base64Data, mimeType }); return result.uploadBlob; } async updateAvatar(dataUrl, profile) { const client = auth.getClient(); // Upload the blob (already resized by crop component) const base64Data = dataUrl.split(',')[1]; const blob = await this.uploadBlob(base64Data, 'image/jpeg'); if (!blob) { throw new Error('Failed to upload avatar'); } // Build input with all profile fields (update requires full object) const input = { displayName: profile.displayName || null, description: profile.description || null, createdAt: profile.createdAt || new Date().toISOString(), avatar: { $type: 'blob', ref: { $link: blob.ref }, mimeType: blob.mimeType, size: blob.size } }; // Update profile await client.mutate(` mutation UpdateProfile($rkey: String!, $input: SocialGrainActorProfileInput!) { updateSocialGrainActorProfile(rkey: $rkey, input: $input) { uri } } `, { rkey: 'self', input }); // Refresh user data await auth.refreshUser(); } async updateProfile(input) { const client = auth.getClient(); await client.mutate(` mutation UpdateProfile($rkey: String!, $input: SocialGrainActorProfileInput!) { updateSocialGrainActorProfile(rkey: $rkey, input: $input) { uri } } `, { rkey: 'self', input }); await auth.refreshUser(); } async createEmptyProfile() { return this.updateProfile({ createdAt: new Date().toISOString() }); } async createReport(subjectUri, reasonType, reason = null) { const client = auth.getClient(); const result = await client.mutate(` mutation CreateReport($subjectUri: String!, $reasonType: ReportReasonType!, $reason: String) { createReport(subjectUri: $subjectUri, reasonType: $reasonType, reason: $reason) { id status createdAt } } `, { subjectUri, reasonType, reason }); return result.createReport; } } export const mutations = new MutationsService();