Skip to content

[pull] main from civitai:main #333

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Feb 7, 2025
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
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "model-share",
"version": "5.0.465",
"version": "5.0.466",
"private": true,
"scripts": {
"start": "next start",
Expand Down
4 changes: 4 additions & 0 deletions src/env/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,10 @@ export const serverSchema = z.object({
VIMEO_SECRET: z.string().optional(),
VIMEO_CLIENT_ID: z.string().optional(),
VIMEO_VIDEO_UPLOAD_URL: z.string().optional(),

// Creator Program Related:
CREATOR_POOL_TAXES: z.coerce.number().optional(),
CREATOR_POOL_PORTION: z.coerce.number().optional(),
});

/**
Expand Down
189 changes: 189 additions & 0 deletions src/pages/user/pool-estimate.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
import { Card, Container, Group, NumberInput, Stack, Text, Title } from '@mantine/core';
import { IconPercentage } from '@tabler/icons-react';
import { useRouter } from 'next/router';
import { useState } from 'react';
import { CurrencyBadge } from '~/components/Currency/CurrencyBadge';
import {
DescriptionTable,
type Props as DescriptionTableProps,
} from '~/components/DescriptionTable/DescriptionTable';
import { Meta } from '~/components/Meta/Meta';
import { useFeatureFlags } from '~/providers/FeatureFlagsProvider';
import { createServerSideProps } from '~/server/utils/server-side-helpers';
import { Currency } from '~/shared/utils/prisma/enums';
import { getLoginLink } from '~/utils/login-helpers';
import { abbreviateNumber } from '~/utils/number-helpers';
import { trpc } from '~/utils/trpc';

export const getServerSideProps = createServerSideProps({
useSession: true,
resolver: async ({ features, session, ctx }) => {
if (!features?.buzz) {
return { notFound: true };
}

if (!session)
return {
redirect: {
destination: getLoginLink({ returnUrl: ctx.resolvedUrl }),
permanent: false,
},
};
},
});



export default function EarnPotential() {
const [bankPortion, setBankPortion] = useState<number>(50);
const [creatorBankPortion, setCreatorBankPortion] = useState<number>(100);
const { query } = useRouter();
const features = useFeatureFlags();

const { data: potential, isLoading } = trpc.buzz.getPoolForecast.useQuery(
{ username: query.username as string },
{ enabled: features.buzz }
);
const poolValue = potential?.poolValue ?? 0;
const poolSize = potential?.poolSize ?? 0;
const earned = potential?.earned ?? 0;


const bankedBuzz = poolSize * bankPortion/100;
const creatorBankedBuzz = earned * creatorBankPortion/100;
const forecastedEarning =
poolValue / bankedBuzz * creatorBankedBuzz;

const buzzCurrencyProps = {
currency: Currency.BUZZ,
loading: isLoading,
formatter: abbreviateNumber,
};
const dollarCurrencyProps = {
currency: Currency.USD,
loading: isLoading,
formatter: (x: number) => abbreviateNumber(x, { decimals: 2 }),
};

const poolDetails: DescriptionTableProps['items'] = [
{
label: 'Pool Value',
info: 'The total value of the creator earning pool',
value: <CurrencyBadge
unitAmount={poolValue}
size="lg"
{...dollarCurrencyProps}
/>,
},
{
label: 'Buzz Earned by All Creators',
info: 'The total amount of Buzz earned by all Creators last month',
value: <CurrencyBadge
unitAmount={poolSize}
size="lg"
{...buzzCurrencyProps}
/>,
},
{
label: 'Portion of All Earned Buzz Banked',
info: 'The portion of all earned Buzz Creators Bank in the month',
value: (
<NumberInput
value={bankPortion}
onChange={(v) => setBankPortion(v ?? 50)}
min={10}
max={80}
step={5}
icon={<IconPercentage />}
/>
),
},
{
label: 'Pool Size',
info: 'The total amount of Buzz in the pool',
value: <CurrencyBadge
unitAmount={bankedBuzz}
size="lg"
{...buzzCurrencyProps}
/>,
},
{
label: 'Your Buzz Earned',
info: 'The total amount of Buzz you earned last month',
value: <CurrencyBadge
unitAmount={earned}
size="lg"
{...buzzCurrencyProps}
/>,
},
{
label: 'Your Bank Portion',
info: 'The amount of earned Buzz you plan to Bank',
value: (
<NumberInput
value={creatorBankPortion}
onChange={(v) => setCreatorBankPortion(v ?? 50)}
min={10}
max={100}
step={5}
icon={<IconPercentage />}
/>
),
},
{
label: 'Your Banked Buzz',
info: 'The total amount of Buzz you\'ve put into the pool',
value: <CurrencyBadge
unitAmount={creatorBankedBuzz}
size="lg"
{...buzzCurrencyProps}
/>,
},
];

return (
<>
<Meta deIndex />
<Container size="md">
<Stack>
<Stack spacing={0}>
<Title mb={0}>Estimated Creator Compensation Pool Earnings</Title>
<Text color="dimmed">
This is an estimate of your potential earnings from the Creator Compensation Pool based on your earnings last month as well as the total earnings of all creators on the platform.
</Text>
</Stack>
<Card p={0} withBorder shadow="xs">
<Card.Section withBorder p="xs">
<Text weight={500} size="lg">
Pool Earning Factors
</Text>
</Card.Section>
<DescriptionTable
items={poolDetails}
labelWidth="30%"
paperProps={{
sx: {
borderLeft: 0,
borderRight: 0,
borderBottom: 0,
},
radius: 0,
}}
/>
</Card>
<Group>
<Text size="xl" weight={900}>
Estimated Earnings:
</Text>
<CurrencyBadge
unitAmount={forecastedEarning}
{...dollarCurrencyProps}
size="xl"
sx={{ fontWeight: 900, fontSize: 24 }}
/>
</Group>
</Stack>
</Container>
</>
);
}
5 changes: 5 additions & 0 deletions src/server/redis/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -494,6 +494,11 @@ export const REDIS_KEYS = {
BASE: 'packed:home-blocks',
},
CACHE_LOCKS: 'cache-lock',
BUZZ: {
POTENTIAL_POOL: 'buzz:potential-pool',
POTENTIAL_POOL_VALUE: 'buzz:potential-pool-value',
EARNED: 'buzz:earned',
},
} as const;

// These are used as subkeys after a dynamic key, such as `user:13:stuff`
Expand Down
6 changes: 6 additions & 0 deletions src/server/routers/buzz.router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import {
claimWatchedAdReward,
getClaimStatus,
getEarnPotential,
getPoolForecast,
} from '~/server/services/buzz.service';
import { isFlagProtected, protectedProcedure, router } from '~/server/trpc';

Expand Down Expand Up @@ -72,6 +73,11 @@ export const buzzRouter = router({
if (!input.username && !input.userId) input.userId = ctx.user.id;
return getEarnPotential(input);
}),
getPoolForecast: buzzProcedure.input(getEarnPotentialSchema).query(({ input, ctx }) => {
if (!ctx.user.isModerator) input.userId = ctx.user.id;
if (!input.username && !input.userId) input.userId = ctx.user.id;
return getPoolForecast(input);
}),
getDailyBuzzCompensation: buzzProcedure
.input(getDailyBuzzCompensationInput)
.query(getDailyCompensationRewardHandler),
Expand Down
86 changes: 86 additions & 0 deletions src/server/services/buzz.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ import { getServerStripe } from '~/server/utils/get-server-stripe';
import { formatDate, stripTime } from '~/utils/date-helpers';
import { QS } from '~/utils/qs';
import { getUserByUsername, getUsers } from './user.service';
import { createCachedObject, fetchThroughCache } from '~/server/utils/cache-helpers';
import { REDIS_KEYS } from '~/server/redis/client';
import { CacheTTL } from '~/server/common/constants';
import { number } from 'zod';
// import { adWatchedReward } from '~/server/rewards';

type AccountType = 'User';
Expand Down Expand Up @@ -820,6 +824,88 @@ export async function getEarnPotential({ userId, username }: GetEarnPotentialSch
return potential;
}

const earnedCache = createCachedObject<{ id: number; earned: number }>({
key: REDIS_KEYS.BUZZ.EARNED,
idKey: 'id',
lookupFn: async (ids) => {
if (ids.length === 0 || !clickhouse) return {};

const results = await clickhouse.$query<{ id: number; earned: number }>`
SELECT
toAccountId as id,
SUM(amount) as earned
FROM buzzTransactions
WHERE type = 'compensation'
AND toAccountType = 'user'
AND toAccountId IN (${ids})
AND toStartOfMonth(date) = toStartOfMonth(subtractMonths(now(), 1))
GROUP BY toAccountId;
`;

return Object.fromEntries(results.map((r) => [r.id, { id: r.id, earned: Number(r.earned) }]));
},
ttl: CacheTTL.day,
});

export async function getPoolForecast({ userId, username }: GetEarnPotentialSchema) {
if (!clickhouse) return;
if (!userId && !username) return;
if (!userId && username) {
const user = await getUserByUsername({ username, select: { id: true } });
if (!user) return;
userId = user.id;
}
if (!userId) return;

const poolSize = await fetchThroughCache(
REDIS_KEYS.BUZZ.POTENTIAL_POOL,
async () => {
const results = await clickhouse!.$query<{ balance: number }>`
SELECT
SUM(amount) AS balance
FROM buzzTransactions
WHERE toAccountType = 'user'
AND type IN ('compensation', 'tip')
AND toAccountId != 0
AND toStartOfMonth(date) = toStartOfMonth(subtractMonths(now(), 1));
`;
if (!results.length) return 135000000;
return results[0].balance;
},
{ ttl: CacheTTL.day }
);

const poolValue = await fetchThroughCache(
REDIS_KEYS.BUZZ.POTENTIAL_POOL_VALUE,
async () => {
const results = await clickhouse!.$query<{ balance: number }>`
SELECT
SUM(amount) / 1000 AS balance
FROM buzzTransactions
WHERE toAccountType = 'user'
AND type = 'purchase'
AND fromAccountId = 0
AND externalTransactionId NOT LIKE 'renewalBonus:%'
AND toStartOfMonth(date) = toStartOfMonth(subtractMonths(now(), 1));
`;
if (!results.length || !env.CREATOR_POOL_TAXES || !env.CREATOR_POOL_PORTION) return 35000;
const gross = results[0].balance;
const taxesAndFees = gross * (env.CREATOR_POOL_TAXES / 100);
const poolValue = (gross - taxesAndFees) * (env.CREATOR_POOL_PORTION / 100);
return poolValue;
},
{ ttl: CacheTTL.day }
);

const results = await earnedCache.fetch(userId);

return {
poolSize,
poolValue,
earned: results[userId]?.earned ?? 0,
};
}

type Row = { modelVersionId: number; date: Date; comp: number; tip: number; total: number };

export const getDailyCompensationRewardByUser = async ({
Expand Down