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
11 changes: 7 additions & 4 deletions packages/common/src/api/tan-query/comments/usePostTextUpdate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export type PostTextUpdateArgs = {
entityId: ID // artist user_id (coin owner)
body: string
mint: string
isMembersOnly?: boolean
}

export const usePostTextUpdate = () => {
Expand All @@ -29,12 +30,13 @@ export const usePostTextUpdate = () => {
entityId: args.entityId,
entityType: 'FanClub',
body: args.body,
mentions: []
}
mentions: [],
isMembersOnly: args.isMembersOnly ?? true
} as any
})
},
onMutate: async (args: PostTextUpdateArgs & { newId?: ID }) => {
const { userId, body, entityId, mint } = args
const { userId, body, entityId, mint, isMembersOnly } = args
const sdk = await audiusSdk()
const newId = await sdk.comments.generateCommentId()
args.newId = newId
Expand All @@ -52,7 +54,8 @@ export const usePostTextUpdate = () => {
replyCount: 0,
replies: undefined,
createdAt: new Date().toISOString(),
updatedAt: undefined
updatedAt: undefined,
isMembersOnly: isMembersOnly ?? true
}

// Prime the individual comment cache
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import logging # pylint: disable=C0302
from typing import List

from sqlalchemy import text as sa_text
from web3 import Web3
from web3.datastructures import AttributeDict

Expand Down Expand Up @@ -1735,3 +1736,201 @@ def test_comment_reaction_validation(app, mocker):
.all()
)
assert len(reaction_notifications) == 0


fan_club_entities = {
"users": [
{"user_id": 1, "wallet": "user1wallet"},
{"user_id": 2, "wallet": "user2wallet"},
],
"tracks": [{"track_id": 1, "owner_id": 1}],
}


def _seed_artist_coin(db, user_id):
"""Create artist_coins table (if absent) and insert a row so fan club validation passes."""
with db.scoped_session() as session:
session.execute(
sa_text(
"CREATE TABLE IF NOT EXISTS artist_coins ("
" user_id integer PRIMARY KEY,"
" mint text NOT NULL,"
" decimals integer NOT NULL DEFAULT 6,"
" ticker text"
")"
)
)
session.execute(
sa_text(
"INSERT INTO artist_coins (user_id, mint, decimals, ticker) "
"VALUES (:uid, :mint, 6, 'TST') ON CONFLICT DO NOTHING"
),
{"uid": user_id, "mint": f"TestMint{user_id}"},
)


def test_fan_club_comment_is_members_only_default(app, mocker):
"""
Fan club comments default to is_members_only=True when the field is omitted.
"""
fan_club_metadata = json.dumps(
{
"entity_id": 1,
"entity_type": "FanClub",
"body": "exclusive update",
}
)

tx_receipts = {
"CreateFanClubComment": [
{
"args": AttributeDict(
{
"_entityId": 100,
"_entityType": "Comment",
"_userId": 1,
"_action": "Create",
"_metadata": f'{{"cid": "", "data": {fan_club_metadata}}}',
"_signer": "user1wallet",
}
)
},
],
}

db, index_transaction = setup_test(app, mocker, fan_club_entities, tx_receipts)
_seed_artist_coin(db, 1)

with db.scoped_session() as session:
index_transaction(session)

comment = session.query(Comment).filter(Comment.comment_id == 100).first()
assert comment is not None
assert comment.is_members_only is True
assert comment.entity_type == "FanClub"


def test_fan_club_comment_is_members_only_false(app, mocker):
"""
Fan club comments respect is_members_only=false from metadata.
"""
fan_club_metadata = json.dumps(
{
"entity_id": 1,
"entity_type": "FanClub",
"body": "public announcement",
"is_members_only": False,
}
)

tx_receipts = {
"CreatePublicFanClubComment": [
{
"args": AttributeDict(
{
"_entityId": 101,
"_entityType": "Comment",
"_userId": 1,
"_action": "Create",
"_metadata": f'{{"cid": "", "data": {fan_club_metadata}}}',
"_signer": "user1wallet",
}
)
},
],
}

db, index_transaction = setup_test(app, mocker, fan_club_entities, tx_receipts)
_seed_artist_coin(db, 1)

with db.scoped_session() as session:
index_transaction(session)

comment = session.query(Comment).filter(Comment.comment_id == 101).first()
assert comment is not None
assert comment.is_members_only is False
assert comment.entity_type == "FanClub"
assert comment.text == "public announcement"


def test_fan_club_comment_is_members_only_true_explicit(app, mocker):
"""
Fan club comments respect is_members_only=true from metadata.
"""
fan_club_metadata = json.dumps(
{
"entity_id": 1,
"entity_type": "FanClub",
"body": "vip content",
"is_members_only": True,
}
)

tx_receipts = {
"CreateMembersOnlyComment": [
{
"args": AttributeDict(
{
"_entityId": 102,
"_entityType": "Comment",
"_userId": 1,
"_action": "Create",
"_metadata": f'{{"cid": "", "data": {fan_club_metadata}}}',
"_signer": "user1wallet",
}
)
},
],
}

db, index_transaction = setup_test(app, mocker, fan_club_entities, tx_receipts)
_seed_artist_coin(db, 1)

with db.scoped_session() as session:
index_transaction(session)

comment = session.query(Comment).filter(Comment.comment_id == 102).first()
assert comment is not None
assert comment.is_members_only is True


def test_track_comment_is_members_only_always_false(app, mocker):
"""
Track comments always have is_members_only=False, even if metadata
contains is_members_only=true.
"""
track_comment_metadata = json.dumps(
{
"entity_id": 1,
"entity_type": "Track",
"body": "great track",
"is_members_only": True, # should be ignored for track comments
}
)

tx_receipts = {
"CreateTrackComment": [
{
"args": AttributeDict(
{
"_entityId": 103,
"_entityType": "Comment",
"_userId": 2,
"_action": "Create",
"_metadata": f'{{"cid": "", "data": {track_comment_metadata}}}',
"_signer": "user2wallet",
}
)
},
],
}

db, index_transaction = setup_test(app, mocker, fan_club_entities, tx_receipts)

with db.scoped_session() as session:
index_transaction(session)

comment = session.query(Comment).filter(Comment.comment_id == 103).first()
assert comment is not None
assert comment.is_members_only is False
assert comment.entity_type == "Track"
1 change: 1 addition & 0 deletions packages/discovery-provider/src/models/comments/comment.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ class Comment(Base, RepresentableMixin):
is_delete = Column(Boolean, default=False)
is_visible = Column(Boolean, default=True)
is_edited = Column(Boolean, default=False)
is_members_only = Column(Boolean, default=False, nullable=False)
txhash = Column(Text, nullable=False)
blockhash = Column(Text, nullable=False)
blocknumber = Column(Integer, ForeignKey("blocks.number"), nullable=False)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -235,13 +235,20 @@ def create_comment(params: ManageEntityParameters):
).scalar()

comment_id = params.entity_id
# Only fan club posts can be members-only; track comments are never gated.
is_members_only = (
bool(metadata.get("is_members_only", True))
if entity_type == FAN_CLUB_ENTITY_TYPE
else False
)
comment_record = Comment(
comment_id=comment_id,
user_id=user_id,
text=metadata["body"],
entity_type=entity_type,
entity_id=stored_entity_id,
track_timestamp_s=metadata["track_timestamp_s"],
is_members_only=bool(is_members_only),
txhash=params.txhash,
blockhash=params.event_blockhash,
blocknumber=params.block_number,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { useFeatureFlag } from '@audius/common/hooks'
import { FeatureFlags } from '@audius/common/services'

import { Flex, Paper, Text } from '@audius/harmony-native'
import { Switch } from 'app/components/core'
import { ComposerInput } from 'app/components/composer-input'

const messages = {
Expand All @@ -23,6 +24,7 @@ type PostUpdateCardProps = {

export const PostUpdateCard = ({ mint }: PostUpdateCardProps) => {
const [messageId, setMessageId] = useState(0)
const [isMembersOnly, setIsMembersOnly] = useState(true)
const { data: currentUserId } = useCurrentUserId()
const { data: coin } = useArtistCoin(mint)
const { mutate: postTextUpdate } = usePostTextUpdate()
Expand All @@ -40,11 +42,12 @@ export const PostUpdateCard = ({ mint }: PostUpdateCardProps) => {
userId: currentUserId,
entityId: coin.ownerId,
body: value.trim(),
mint
mint,
isMembersOnly
})
setMessageId((prev) => prev + 1)
},
[currentUserId, coin?.ownerId, mint, postTextUpdate]
[currentUserId, coin?.ownerId, mint, postTextUpdate, isMembersOnly]
)

if (!isOwner || !isTextPostPostingEnabled) return null
Expand Down Expand Up @@ -75,6 +78,10 @@ export const PostUpdateCard = ({ mint }: PostUpdateCardProps) => {
<Text variant='label' size='s'>
{messages.membersOnly}
</Text>
<Switch
value={isMembersOnly}
onValueChange={setIsMembersOnly}
/>
</Flex>
</Flex>
</Paper>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,15 @@ export const TextPostCard = ({ commentId, mint }: TextPostCardProps) => {
<Text variant='body' size='m'>
{comment.message}
</Text>
<Flex direction='row' alignItems='center' gap='xs'>
<Flex direction='row' alignItems='center' gap='s'>
{comment.isMembersOnly !== false ? (
<Flex row alignItems='center' gap='xs'>
<IconLock size='s' color='subdued' />
<Text variant='body' size='xs' color='subdued'>
{messages.membersOnly}
</Text>
</Flex>
) : null}
<FavoriteButton
onPress={handleReact}
isActive={comment.isCurrentUserReacted}
Expand Down
9 changes: 7 additions & 2 deletions packages/sdk/src/sdk/api/comments/CommentsAPI.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,8 @@ export class CommentsApi extends GeneratedCommentsApi {
mentions,
parentCommentId,
trackTimestampS,
entityId
entityId,
isMembersOnly
} = metadata
const newCommentId = commentId ?? (await this.generateCommentId())

Expand All @@ -96,6 +97,9 @@ export class CommentsApi extends GeneratedCommentsApi {
body,
entity_id: entityId
}
if (isMembersOnly !== undefined) {
data.is_members_only = isMembersOnly
}
if (mentions !== undefined && mentions.length > 0) {
data.mentions = mentions
}
Expand Down Expand Up @@ -138,7 +142,8 @@ export class CommentsApi extends GeneratedCommentsApi {
commentId: md.commentId,
parentCommentId: md.parentId,
trackTimestampS: md.trackTimestampS,
mentions: md.mentions
mentions: md.mentions,
isMembersOnly: (md as any).isMembersOnly
})
}
return await this.createCommentWithEntityManager({
Expand Down
3 changes: 2 additions & 1 deletion packages/sdk/src/sdk/api/comments/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,8 @@ export const CreateCommentSchema = z
commentId: z.optional(z.number()),
parentCommentId: z.optional(z.number()),
trackTimestampS: z.optional(z.number()),
mentions: z.optional(z.array(z.number()))
mentions: z.optional(z.array(z.number())),
isMembersOnly: z.optional(z.boolean())
})
.strict()
.refine(
Expand Down
Loading
Loading