diff --git a/keyserver/src/creators/farcaster-channel-tag-creator.js b/keyserver/src/creators/farcaster-channel-tag-creator.js --- a/keyserver/src/creators/farcaster-channel-tag-creator.js +++ b/keyserver/src/creators/farcaster-channel-tag-creator.js @@ -12,10 +12,17 @@ } from 'lib/types/community-types.js'; import { ServerError } from 'lib/utils/errors.js'; +import { + dbQuery, + SQL, + MYSQL_DUPLICATE_ENTRY_FOR_KEY_ERROR_CODE, +} from '../database/database.js'; +import { fetchCommunityInfos } from '../fetchers/community-fetchers.js'; import { uploadBlob, assignHolder, download, + deleteBlob, type BlobOperationResult, type BlobDownloadResult, } from '../services/blob.js'; @@ -25,13 +32,22 @@ viewer: Viewer, request: CreateOrUpdateFarcasterChannelTagRequest, ): Promise { - const { commCommunityID, farcasterChannelID } = request; - if (DISABLE_TAGGING_FARCASTER_CHANNEL) { throw new ServerError('internal_error'); } - const blobDownload = await getFarcasterChannelTagBlob(farcasterChannelID); + const [serverCommunityInfos, blobDownload] = await Promise.all([ + fetchCommunityInfos(viewer), + getFarcasterChannelTagBlob(request.farcasterChannelID), + ]); + + const communityInfo = serverCommunityInfos.find( + community => community.id === request.commCommunityID, + ); + + if (!communityInfo) { + throw new ServerError('invalid_parameters'); + } if (blobDownload.found) { throw new ServerError('already_in_use'); @@ -40,8 +56,8 @@ const blobHolder = uuid.v4(); const blobResult = await uploadFarcasterChannelTagBlob( - commCommunityID, - farcasterChannelID, + request.commCommunityID, + request.farcasterChannelID, blobHolder, ); @@ -53,9 +69,62 @@ } } + const query = SQL` + START TRANSACTION; + + SELECT farcaster_channel_id, blob_holder + INTO @currentFarcasterChannelID, @currentBlobHolder + FROM communities + WHERE id = ${request.commCommunityID} + FOR UPDATE; + + UPDATE communities + SET farcaster_channel_id = ${request.farcasterChannelID}, + blob_holder = ${blobHolder} + WHERE id = ${request.commCommunityID}; + + COMMIT; + + SELECT + @currentFarcasterChannelID AS oldFarcasterChannelID, + @currentBlobHolder AS oldBlobHolder; + `; + + try { + const [transactionResult] = await dbQuery(query, { + multipleStatements: true, + }); + + const selectResult = transactionResult.pop(); + const [{ oldFarcasterChannelID, oldBlobHolder }] = selectResult; + + if (oldFarcasterChannelID && oldBlobHolder) { + await deleteBlob( + { + hash: farcasterChannelTagBlobHash(oldFarcasterChannelID), + holder: oldBlobHolder, + }, + true, + ); + } + } catch (error) { + await deleteBlob( + { + hash: farcasterChannelTagBlobHash(request.farcasterChannelID), + holder: blobHolder, + }, + true, + ); + + if (error.errno === MYSQL_DUPLICATE_ENTRY_FOR_KEY_ERROR_CODE) { + throw new ServerError('already_in_use'); + } + throw new ServerError('invalid_parameters'); + } + return { - commCommunityID, - blobHolder, + commCommunityID: request.commCommunityID, + farcasterChannelID: request.farcasterChannelID, }; } diff --git a/lib/actions/community-actions.js b/lib/actions/community-actions.js --- a/lib/actions/community-actions.js +++ b/lib/actions/community-actions.js @@ -48,7 +48,7 @@ return { commCommunityID: response.commCommunityID, - blobHolder: response.blobHolder, + farcasterChannelID: response.farcasterChannelID, }; }; diff --git a/lib/types/community-types.js b/lib/types/community-types.js --- a/lib/types/community-types.js +++ b/lib/types/community-types.js @@ -22,7 +22,7 @@ export type CreateOrUpdateFarcasterChannelTagResponse = { +commCommunityID: string, - +blobHolder: string, + +farcasterChannelID: string, }; export type DeleteFarcasterChannelTagRequest = { diff --git a/lib/types/validators/farcaster-channel-tag-validators.js b/lib/types/validators/farcaster-channel-tag-validators.js --- a/lib/types/validators/farcaster-channel-tag-validators.js +++ b/lib/types/validators/farcaster-channel-tag-validators.js @@ -8,5 +8,5 @@ export const createOrUpdateFarcasterChannelTagResponseValidator: TInterface = tShape({ commCommunityID: tID, - blobHolder: t.String, + farcasterChannelID: t.String, });