feat: comment fee control (#1768)
* feat: comment fee control * update typeDefs for unarchiving territories * review: move functions to top level; consider saloon items * ux: cleaner post/reply cost section * hotfix: handle salon replies * bios don't have subs + simplify root query * move reply cost to accordian --------- Co-authored-by: Keyan <34140557+huumn@users.noreply.github.com> Co-authored-by: k00b <k00b@stacker.news>
This commit is contained in:
		
							parent
							
								
									ac321be3cd
								
							
						
					
					
						commit
						5c2aa979ea
					
				| @ -13,9 +13,33 @@ export const paymentMethods = [ | |||||||
|   PAID_ACTION_PAYMENT_METHODS.PESSIMISTIC |   PAID_ACTION_PAYMENT_METHODS.PESSIMISTIC | ||||||
| ] | ] | ||||||
| 
 | 
 | ||||||
|  | export const DEFAULT_ITEM_COST = 1000n | ||||||
|  | 
 | ||||||
|  | export async function getBaseCost ({ models, bio, parentId, subName }) { | ||||||
|  |   if (bio) return DEFAULT_ITEM_COST | ||||||
|  | 
 | ||||||
|  |   if (parentId) { | ||||||
|  |     // the subname is stored in the root item of the thread
 | ||||||
|  |     const parent = await models.item.findFirst({ | ||||||
|  |       where: { id: Number(parentId) }, | ||||||
|  |       include: { | ||||||
|  |         root: { include: { sub: true } }, | ||||||
|  |         sub: true | ||||||
|  |       } | ||||||
|  |     }) | ||||||
|  | 
 | ||||||
|  |     const root = parent.root ?? parent | ||||||
|  | 
 | ||||||
|  |     if (!root.sub) return DEFAULT_ITEM_COST | ||||||
|  |     return satsToMsats(root.sub.replyCost) | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   const sub = await models.sub.findUnique({ where: { name: subName } }) | ||||||
|  |   return satsToMsats(sub.baseCost) | ||||||
|  | } | ||||||
|  | 
 | ||||||
| export async function getCost ({ subName, parentId, uploadIds, boost = 0, bio }, { models, me }) { | export async function getCost ({ subName, parentId, uploadIds, boost = 0, bio }, { models, me }) { | ||||||
|   const sub = (parentId || bio) ? null : await models.sub.findUnique({ where: { name: subName } }) |   const baseCost = await getBaseCost({ models, bio, parentId, subName }) | ||||||
|   const baseCost = sub ? satsToMsats(sub.baseCost) : 1000n |  | ||||||
| 
 | 
 | ||||||
|   // cost = baseCost * 10^num_items_in_10m * 100 (anon) or 1 (user) + upload fees + boost
 |   // cost = baseCost * 10^num_items_in_10m * 100 (anon) or 1 (user) + upload fees + boost
 | ||||||
|   const [{ cost }] = await models.$queryRaw` |   const [{ cost }] = await models.$queryRaw` | ||||||
|  | |||||||
| @ -16,6 +16,7 @@ export default gql` | |||||||
| 
 | 
 | ||||||
|   extend type Mutation { |   extend type Mutation { | ||||||
|     upsertSub(oldName: String, name: String!, desc: String, baseCost: Int!, |     upsertSub(oldName: String, name: String!, desc: String, baseCost: Int!, | ||||||
|  |       replyCost: Int!, | ||||||
|       postTypes: [String!]!, |       postTypes: [String!]!, | ||||||
|       billingType: String!, billingAutoRenew: Boolean!, |       billingType: String!, billingAutoRenew: Boolean!, | ||||||
|       moderated: Boolean!, nsfw: Boolean!): SubPaidAction! |       moderated: Boolean!, nsfw: Boolean!): SubPaidAction! | ||||||
| @ -24,7 +25,7 @@ export default gql` | |||||||
|     toggleSubSubscription(name: String!): Boolean! |     toggleSubSubscription(name: String!): Boolean! | ||||||
|     transferTerritory(subName: String!, userName: String!): Sub |     transferTerritory(subName: String!, userName: String!): Sub | ||||||
|     unarchiveTerritory(name: String!, desc: String, baseCost: Int!, |     unarchiveTerritory(name: String!, desc: String, baseCost: Int!, | ||||||
|       postTypes: [String!]!, |       replyCost: Int!, postTypes: [String!]!, | ||||||
|       billingType: String!, billingAutoRenew: Boolean!, |       billingType: String!, billingAutoRenew: Boolean!, | ||||||
|       moderated: Boolean!, nsfw: Boolean!): SubPaidAction! |       moderated: Boolean!, nsfw: Boolean!): SubPaidAction! | ||||||
|   } |   } | ||||||
| @ -45,6 +46,7 @@ export default gql` | |||||||
|     billedLastAt: Date! |     billedLastAt: Date! | ||||||
|     billPaidUntil: Date |     billPaidUntil: Date | ||||||
|     baseCost: Int! |     baseCost: Int! | ||||||
|  |     replyCost: Int! | ||||||
|     status: String! |     status: String! | ||||||
|     moderated: Boolean! |     moderated: Boolean! | ||||||
|     moderatedCount: Int! |     moderatedCount: Int! | ||||||
|  | |||||||
| @ -161,7 +161,7 @@ export default forwardRef(function Reply ({ | |||||||
|       {reply && |       {reply && | ||||||
|         <div className={styles.reply}> |         <div className={styles.reply}> | ||||||
|           <FeeButtonProvider |           <FeeButtonProvider | ||||||
|             baseLineItems={postCommentBaseLineItems({ baseCost: 1, comment: true, me: !!me })} |             baseLineItems={postCommentBaseLineItems({ baseCost: sub?.replyCost ?? 1, comment: true, me: !!me })} | ||||||
|             useRemoteLineItems={postCommentUseRemoteLineItems({ parentId: item.id, me: !!me })} |             useRemoteLineItems={postCommentUseRemoteLineItems({ parentId: item.id, me: !!me })} | ||||||
|           > |           > | ||||||
|             <Form |             <Form | ||||||
|  | |||||||
| @ -91,6 +91,7 @@ export default function TerritoryForm ({ sub }) { | |||||||
|           name: sub?.name || '', |           name: sub?.name || '', | ||||||
|           desc: sub?.desc || '', |           desc: sub?.desc || '', | ||||||
|           baseCost: sub?.baseCost || 10, |           baseCost: sub?.baseCost || 10, | ||||||
|  |           replyCost: sub?.replyCost || 1, | ||||||
|           postTypes: sub?.postTypes || POST_TYPES, |           postTypes: sub?.postTypes || POST_TYPES, | ||||||
|           billingType: sub?.billingType || 'MONTHLY', |           billingType: sub?.billingType || 'MONTHLY', | ||||||
|           billingAutoRenew: sub?.billingAutoRenew || false, |           billingAutoRenew: sub?.billingAutoRenew || false, | ||||||
| @ -234,6 +235,13 @@ export default function TerritoryForm ({ sub }) { | |||||||
|           header={<div style={{ fontWeight: 'bold', fontSize: '92%' }}>options</div>} |           header={<div style={{ fontWeight: 'bold', fontSize: '92%' }}>options</div>} | ||||||
|           body={ |           body={ | ||||||
|             <> |             <> | ||||||
|  |               <Input | ||||||
|  |                 label='reply cost' | ||||||
|  |                 name='replyCost' | ||||||
|  |                 type='number' | ||||||
|  |                 required | ||||||
|  |                 append={<InputGroup.Text className='text-monospace'>sats</InputGroup.Text>} | ||||||
|  |               /> | ||||||
|               <BootstrapForm.Label>moderation</BootstrapForm.Label> |               <BootstrapForm.Label>moderation</BootstrapForm.Label> | ||||||
|               <Checkbox |               <Checkbox | ||||||
|                 inline |                 inline | ||||||
|  | |||||||
| @ -57,9 +57,16 @@ export function TerritoryInfo ({ sub }) { | |||||||
|           <span> on </span> |           <span> on </span> | ||||||
|           <span className='fw-bold'>{new Date(sub.createdAt).toDateString()}</span> |           <span className='fw-bold'>{new Date(sub.createdAt).toDateString()}</span> | ||||||
|         </div> |         </div> | ||||||
|         <div className='text-muted'> |         <div className='d-flex'> | ||||||
|           <span>post cost </span> |           <div className='text-muted'> | ||||||
|           <span className='fw-bold'>{numWithUnits(sub.baseCost)}</span> |             <span>post cost </span> | ||||||
|  |             <span className='fw-bold'>{numWithUnits(sub.baseCost)}</span> | ||||||
|  |           </div> | ||||||
|  |           <span className='px-1'> \ </span> | ||||||
|  |           <div className='text-muted'> | ||||||
|  |             <span>reply cost </span> | ||||||
|  |             <span className='fw-bold'>{numWithUnits(sub.replyCost)}</span> | ||||||
|  |           </div> | ||||||
|         </div> |         </div> | ||||||
|         <TerritoryBillingLine sub={sub} /> |         <TerritoryBillingLine sub={sub} /> | ||||||
|       </CardFooter> |       </CardFooter> | ||||||
|  | |||||||
| @ -35,6 +35,7 @@ export const ITEM_FIELDS = gql` | |||||||
|       meMuteSub |       meMuteSub | ||||||
|       meSubscription |       meSubscription | ||||||
|       nsfw |       nsfw | ||||||
|  |       replyCost | ||||||
|     } |     } | ||||||
|     otsHash |     otsHash | ||||||
|     position |     position | ||||||
|  | |||||||
| @ -264,10 +264,10 @@ export const UPDATE_COMMENT = gql` | |||||||
| export const UPSERT_SUB = gql` | export const UPSERT_SUB = gql` | ||||||
|   ${PAID_ACTION} |   ${PAID_ACTION} | ||||||
|   mutation upsertSub($oldName: String, $name: String!, $desc: String, $baseCost: Int!, |   mutation upsertSub($oldName: String, $name: String!, $desc: String, $baseCost: Int!, | ||||||
|     $postTypes: [String!]!, $billingType: String!, |     $replyCost: Int!, $postTypes: [String!]!, $billingType: String!, | ||||||
|     $billingAutoRenew: Boolean!, $moderated: Boolean!, $nsfw: Boolean!) { |     $billingAutoRenew: Boolean!, $moderated: Boolean!, $nsfw: Boolean!) { | ||||||
|       upsertSub(oldName: $oldName, name: $name, desc: $desc, baseCost: $baseCost, |       upsertSub(oldName: $oldName, name: $name, desc: $desc, baseCost: $baseCost, | ||||||
|         postTypes: $postTypes, billingType: $billingType, |         replyCost: $replyCost, postTypes: $postTypes, billingType: $billingType, | ||||||
|         billingAutoRenew: $billingAutoRenew, moderated: $moderated, nsfw: $nsfw) { |         billingAutoRenew: $billingAutoRenew, moderated: $moderated, nsfw: $nsfw) { | ||||||
|       result { |       result { | ||||||
|         name |         name | ||||||
| @ -279,10 +279,10 @@ export const UPSERT_SUB = gql` | |||||||
| export const UNARCHIVE_TERRITORY = gql` | export const UNARCHIVE_TERRITORY = gql` | ||||||
|   ${PAID_ACTION} |   ${PAID_ACTION} | ||||||
|   mutation unarchiveTerritory($name: String!, $desc: String, $baseCost: Int!, |   mutation unarchiveTerritory($name: String!, $desc: String, $baseCost: Int!, | ||||||
|     $postTypes: [String!]!, $billingType: String!, |     $replyCost: Int!, $postTypes: [String!]!, $billingType: String!, | ||||||
|     $billingAutoRenew: Boolean!, $moderated: Boolean!, $nsfw: Boolean!) { |     $billingAutoRenew: Boolean!, $moderated: Boolean!, $nsfw: Boolean!) { | ||||||
|       unarchiveTerritory(name: $name, desc: $desc, baseCost: $baseCost, |       unarchiveTerritory(name: $name, desc: $desc, baseCost: $baseCost, | ||||||
|         postTypes: $postTypes, billingType: $billingType, |         replyCost: $replyCost, postTypes: $postTypes, billingType: $billingType, | ||||||
|         billingAutoRenew: $billingAutoRenew, moderated: $moderated, nsfw: $nsfw) { |         billingAutoRenew: $billingAutoRenew, moderated: $moderated, nsfw: $nsfw) { | ||||||
|       result { |       result { | ||||||
|         name |         name | ||||||
|  | |||||||
| @ -25,6 +25,7 @@ export const SUB_FIELDS = gql` | |||||||
|     billedLastAt |     billedLastAt | ||||||
|     billPaidUntil |     billPaidUntil | ||||||
|     baseCost |     baseCost | ||||||
|  |     replyCost | ||||||
|     userId |     userId | ||||||
|     desc |     desc | ||||||
|     status |     status | ||||||
|  | |||||||
| @ -317,6 +317,9 @@ export function territorySchema (args) { | |||||||
|     baseCost: intValidator |     baseCost: intValidator | ||||||
|       .min(1, 'must be at least 1') |       .min(1, 'must be at least 1') | ||||||
|       .max(100000, 'must be at most 100k'), |       .max(100000, 'must be at most 100k'), | ||||||
|  |     replyCost: intValidator | ||||||
|  |       .min(1, 'must be at least 1') | ||||||
|  |       .max(100000, 'must be at most 100k'), | ||||||
|     postTypes: array().of(string().oneOf(POST_TYPES)).min(1, 'must support at least one post type'), |     postTypes: array().of(string().oneOf(POST_TYPES)).min(1, 'must support at least one post type'), | ||||||
|     billingType: string().required('required').oneOf(TERRITORY_BILLING_TYPES, 'required'), |     billingType: string().required('required').oneOf(TERRITORY_BILLING_TYPES, 'required'), | ||||||
|     nsfw: boolean() |     nsfw: boolean() | ||||||
|  | |||||||
| @ -0,0 +1,2 @@ | |||||||
|  | -- AlterTable | ||||||
|  | ALTER TABLE "Sub" ADD COLUMN     "replyCost" INTEGER NOT NULL DEFAULT 1; | ||||||
| @ -737,6 +737,7 @@ model Sub { | |||||||
|   rankingType      RankingType |   rankingType      RankingType | ||||||
|   allowFreebies    Boolean     @default(true) |   allowFreebies    Boolean     @default(true) | ||||||
|   baseCost         Int         @default(1) |   baseCost         Int         @default(1) | ||||||
|  |   replyCost        Int         @default(1) | ||||||
|   rewardsPct       Int         @default(50) |   rewardsPct       Int         @default(50) | ||||||
|   desc             String? |   desc             String? | ||||||
|   status           Status      @default(ACTIVE) |   status           Status      @default(ACTIVE) | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user