diff --git a/keyserver/src/fetchers/community-fetchers.js b/keyserver/src/fetchers/community-fetchers.js --- a/keyserver/src/fetchers/community-fetchers.js +++ b/keyserver/src/fetchers/community-fetchers.js @@ -44,4 +44,25 @@ return communityInfos; } -export { fetchCommunityInfos }; +async function checkIfCommunityHasFarcasterChannelTag( + viewer: Viewer, + communityID: string, +): Promise { + if (!viewer.loggedIn) { + return false; + } + + const query = SQL` + SELECT c.farcaster_channel_id as farcasterChannelID + FROM communities c + WHERE c.id = ${communityID} + `; + + const [result] = await dbQuery(query); + + const communityInfo = result[0]; + + return !!communityInfo?.farcasterChannelID; +} + +export { fetchCommunityInfos, checkIfCommunityHasFarcasterChannelTag }; diff --git a/keyserver/src/responders/thread-responders.js b/keyserver/src/responders/thread-responders.js --- a/keyserver/src/responders/thread-responders.js +++ b/keyserver/src/responders/thread-responders.js @@ -3,6 +3,7 @@ import t from 'tcomb'; import type { TInterface, TUnion } from 'tcomb'; +import { threadSubscriptionValidator } from 'lib/types/subscription-types.js'; import { userSurfacedPermissionValidator } from 'lib/types/thread-permission-types.js'; import { threadTypes } from 'lib/types/thread-types-enum.js'; import { @@ -179,6 +180,7 @@ threadID: tID, calendarQuery: t.maybe(entryQueryInputValidator), inviteLinkSecret: t.maybe(t.String), + defaultSubscription: t.maybe(threadSubscriptionValidator), }); async function threadJoinResponder( diff --git a/keyserver/src/updaters/thread-updaters.js b/keyserver/src/updaters/thread-updaters.js --- a/keyserver/src/updaters/thread-updaters.js +++ b/keyserver/src/updaters/thread-updaters.js @@ -45,6 +45,7 @@ import createMessages from '../creators/message-creator.js'; import { createUpdates } from '../creators/update-creator.js'; import { dbQuery, SQL } from '../database/database.js'; +import { checkIfCommunityHasFarcasterChannelTag } from '../fetchers/community-fetchers.js'; import { checkIfInviteLinkIsValid } from '../fetchers/link-fetchers.js'; import { fetchMessageInfoByID } from '../fetchers/message-fetchers.js'; import { @@ -816,16 +817,35 @@ throw new ServerError('not_logged_in'); } - const permissionCheck = request.inviteLinkSecret - ? checkIfInviteLinkIsValid(request.inviteLinkSecret, request.threadID) - : checkThreadPermission( - viewer, + const permissionPromise = (async () => { + if (request.inviteLinkSecret) { + return await checkIfInviteLinkIsValid( + request.inviteLinkSecret, request.threadID, - threadPermissions.JOIN_THREAD, ); + } + + const threadPermissionPromise = checkThreadPermission( + viewer, + request.threadID, + threadPermissions.JOIN_THREAD, + ); + + const communityFarcasterChannelTagPromise = + checkIfCommunityHasFarcasterChannelTag(viewer, request.threadID); + + const [threadPermission, hasCommunityFarcasterChannelTag] = + await Promise.all([ + threadPermissionPromise, + communityFarcasterChannelTagPromise, + ]); + + return threadPermission || hasCommunityFarcasterChannelTag; + })(); + const [isMember, hasPermission] = await Promise.all([ fetchViewerIsMember(viewer, request.threadID), - permissionCheck, + permissionPromise, ]); if (!hasPermission) { throw new ServerError('invalid_parameters'); @@ -855,7 +875,9 @@ } } - const changeset = await changeRole(request.threadID, [viewer.userID], null); + const changeset = await changeRole(request.threadID, [viewer.userID], null, { + defaultSubscription: request.defaultSubscription, + }); const membershipResult = await commitMembershipChangeset(viewer, changeset, { calendarQuery, diff --git a/lib/shared/thread-utils.js b/lib/shared/thread-utils.js --- a/lib/shared/thread-utils.js +++ b/lib/shared/thread-utils.js @@ -54,6 +54,7 @@ minimallyEncodeThreadCurrentUserInfo, } from '../types/minimally-encoded-thread-permissions-types.js'; import { userRelationshipStatus } from '../types/relationship-types.js'; +import { defaultThreadSubscription } from '../types/subscription-types.js'; import { threadPermissionPropagationPrefixes, threadPermissions, @@ -429,11 +430,6 @@ +sourceMessageID?: string, }; -const defaultSubscription = { - pushNotifs: false, - home: false, -}; - function createPendingThread({ viewerID, threadType, @@ -497,7 +493,7 @@ role: role.id, permissions: membershipPermissions, isSender: false, - subscription: defaultSubscription, + subscription: defaultThreadSubscription, }), ), roles: { @@ -506,7 +502,7 @@ currentUser: minimallyEncodeThreadCurrentUserInfo({ role: role.id, permissions: membershipPermissions, - subscription: defaultSubscription, + subscription: defaultThreadSubscription, unread: false, }), repliesCount: 0, @@ -543,7 +539,7 @@ currentUser: minimallyEncodeThreadCurrentUserInfo({ role: role.id, permissions: membershipPermissions, - subscription: defaultSubscription, + subscription: defaultThreadSubscription, unread: false, }), repliesCount: 0, @@ -805,7 +801,7 @@ currentUser = { role: null, permissions: currentUserPermissions, - subscription: defaultSubscription, + subscription: defaultThreadSubscription, unread: null, }; } diff --git a/lib/types/subscription-types.js b/lib/types/subscription-types.js --- a/lib/types/subscription-types.js +++ b/lib/types/subscription-types.js @@ -5,7 +5,7 @@ import { tShape } from '../utils/validation-utils.js'; -export const threadSubscriptions = Object.freeze({ +const threadSubscriptions = Object.freeze({ home: 'home', pushNotifs: 'pushNotifs', }); @@ -31,3 +31,10 @@ threadID: string, subscription: ThreadSubscription, }; + +const defaultThreadSubscription: ThreadSubscription = { + home: false, + pushNotifs: false, +}; + +export { defaultThreadSubscription, threadSubscriptions }; diff --git a/lib/types/thread-types.js b/lib/types/thread-types.js --- a/lib/types/thread-types.js +++ b/lib/types/thread-types.js @@ -340,11 +340,13 @@ +threadID: string, +calendarQuery?: ?CalendarQuery, +inviteLinkSecret?: string, + +defaultSubscription?: ThreadSubscription, }; export type ClientThreadJoinRequest = { +threadID: string, +calendarQuery: CalendarQuery, +inviteLinkSecret?: string, + +defaultSubscription?: ThreadSubscription, }; export type ThreadJoinResult = { +updatesResult: { diff --git a/native/components/auto-join-community-handler.react.js b/native/components/auto-join-community-handler.react.js --- a/native/components/auto-join-community-handler.react.js +++ b/native/components/auto-join-community-handler.react.js @@ -14,6 +14,7 @@ import { farcasterChannelTagBlobHash } from 'lib/shared/community-utils.js'; import type { AuthMetadata } from 'lib/shared/identity-client-context.js'; import { IdentityClientContext } from 'lib/shared/identity-client-context.js'; +import { defaultThreadSubscription } from 'lib/types/subscription-types.js'; import { authoritativeKeyserverID } from 'lib/utils/authoritative-keyserver.js'; import { getBlobFetchableURL } from 'lib/utils/blob-service.js'; import { useCurrentUserFID } from 'lib/utils/farcaster-utils.js'; @@ -68,6 +69,7 @@ { type: 'threads', threadIDs: [communityID] }, ], }, + defaultSubscription: defaultThreadSubscription, }); }, [calendarQuery, joinThread],