diff --git a/package-lock.json b/package-lock.json index b2e12516c7..d4f374e94c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -133,7 +133,7 @@ "node-os-utils": "^1.3.7", "nodemailer": "^6.8.0", "openai": "^4.73.0", - "p-limit": "^5.0.0", + "p-limit": "^6.2.0", "path-to-regexp": "^6.2.1", "pg": "^8.11.3", "posthog-js": "^1.77.0", @@ -16015,11 +16015,12 @@ "integrity": "sha512-TvAWxi0nDe1j/rtMcWcIj94+Ffe6n7zhow33h40SKxmsmozs6dz/e+EajymfoFcHd7sxNn8yHM8839uixMOV6g==" }, "node_modules/p-limit": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-5.0.0.tgz", - "integrity": "sha512-/Eaoq+QyLSiXQ4lyYV23f14mZRQcXnxfHrN0vCai+ak9G0pp9iEQukIIZq5NccEvwRB8PUnZT0KsOoDCINS1qQ==", + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-6.2.0.tgz", + "integrity": "sha512-kuUqqHNUqoIWp/c467RI4X6mmyuojY5jGutNU0wVTmEOOfcuwLqyMVoAi9MKi2Ak+5i9+nhmrK4ufZE8069kHA==", + "license": "MIT", "dependencies": { - "yocto-queue": "^1.0.0" + "yocto-queue": "^1.1.1" }, "engines": { "node": ">=18" @@ -21386,9 +21387,10 @@ } }, "node_modules/yocto-queue": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.0.0.tgz", - "integrity": "sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.2.0.tgz", + "integrity": "sha512-KHBC7z61OJeaMGnF3wqNZj+GGNXOyypZviiKpQeiHirG5Ib1ImwcLBH70rbMSkKfSmUNBsdf2PwaEJtKvgmkNw==", + "license": "MIT", "engines": { "node": ">=12.20" }, @@ -32551,11 +32553,11 @@ "integrity": "sha512-TvAWxi0nDe1j/rtMcWcIj94+Ffe6n7zhow33h40SKxmsmozs6dz/e+EajymfoFcHd7sxNn8yHM8839uixMOV6g==" }, "p-limit": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-5.0.0.tgz", - "integrity": "sha512-/Eaoq+QyLSiXQ4lyYV23f14mZRQcXnxfHrN0vCai+ak9G0pp9iEQukIIZq5NccEvwRB8PUnZT0KsOoDCINS1qQ==", + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-6.2.0.tgz", + "integrity": "sha512-kuUqqHNUqoIWp/c467RI4X6mmyuojY5jGutNU0wVTmEOOfcuwLqyMVoAi9MKi2Ak+5i9+nhmrK4ufZE8069kHA==", "requires": { - "yocto-queue": "^1.0.0" + "yocto-queue": "^1.1.1" } }, "p-locate": { @@ -36413,9 +36415,9 @@ "devOptional": true }, "yocto-queue": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.0.0.tgz", - "integrity": "sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==" + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.2.0.tgz", + "integrity": "sha512-KHBC7z61OJeaMGnF3wqNZj+GGNXOyypZviiKpQeiHirG5Ib1ImwcLBH70rbMSkKfSmUNBsdf2PwaEJtKvgmkNw==" }, "yoga-layout": { "version": "2.0.1", diff --git a/package.json b/package.json index 0961cd5abe..e9c58667e7 100644 --- a/package.json +++ b/package.json @@ -177,7 +177,7 @@ "node-os-utils": "^1.3.7", "nodemailer": "^6.8.0", "openai": "^4.73.0", - "p-limit": "^5.0.0", + "p-limit": "^6.2.0", "path-to-regexp": "^6.2.1", "pg": "^8.11.3", "posthog-js": "^1.77.0", diff --git a/prisma/migrations/20250313162509_drop_log/migration.sql b/prisma/migrations/20250313162509_drop_log/migration.sql index 9bf59626d2..ddf1893b47 100644 --- a/prisma/migrations/20250313162509_drop_log/migration.sql +++ b/prisma/migrations/20250313162509_drop_log/migration.sql @@ -1,4 +1,2 @@ -- DropTable DROP TABLE "Log"; - - diff --git a/prisma/migrations/20250318165942_moderation_rules_created_by/migration.sql b/prisma/migrations/20250318165942_moderation_rules_created_by/migration.sql new file mode 100644 index 0000000000..a9212429f7 --- /dev/null +++ b/prisma/migrations/20250318165942_moderation_rules_created_by/migration.sql @@ -0,0 +1,5 @@ +-- AlterTable +ALTER TABLE "ModerationRule" ADD COLUMN "createdById" INTEGER NOT NULL; + +-- AddForeignKey +ALTER TABLE "ModerationRule" ADD CONSTRAINT "ModerationRule_createdById_fkey" FOREIGN KEY ("createdById") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/prisma/programmability/update_nsfw_level.sql b/prisma/programmability/update_nsfw_level.sql index d6ed72f6c3..5da77eda15 100644 --- a/prisma/programmability/update_nsfw_level.sql +++ b/prisma/programmability/update_nsfw_level.sql @@ -12,9 +12,9 @@ BEGIN WHEN bool_or(t."nsfwLevel" = 2) THEN 2 ELSE 1 END "nsfwLevel" - FROM "TagsOnImage" toi + FROM "TagsOnImageDetails" toi LEFT JOIN "Tag" t ON t.id = toi."tagId" AND t."nsfwLevel" > 1 - WHERE toi."imageId" = ANY(image_ids) AND toi."disabledAt" IS NULL + WHERE toi."imageId" = ANY(image_ids) AND NOT toi."disabled" GROUP BY toi."imageId" ) UPDATE "Image" i SET diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 8ccb88ea38..5abb186923 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -346,6 +346,7 @@ model User { cashWithdrawals CashWithdrawal[] bids Bid[] recurringBids BidRecurring[] + moderationRules ModerationRule[] @@index([deletedAt]) } @@ -3454,6 +3455,9 @@ model ModerationRule { enabled Boolean @default(true) order Int? reason String? + + createdById Int + createdBy User @relation(fields: [createdById], references: [id], onDelete: Cascade) } /// @view diff --git a/src/components/Resource/Forms/TrainingSelectFile.tsx b/src/components/Resource/Forms/TrainingSelectFile.tsx index 779aa42962..d45c189ec4 100644 --- a/src/components/Resource/Forms/TrainingSelectFile.tsx +++ b/src/components/Resource/Forms/TrainingSelectFile.tsx @@ -416,7 +416,7 @@ export default function TrainingSelectFile({ )} - {canGenerateWithEpochBool && ( + {canGenerateWithEpochBool && features.privateModels && ( { - const params = schema.parse(req.query); - console.dir({ params }, { depth: null }); - console.time('MIGRATION_TIMER'); - await dataProcessor({ - params, - runContext: res, - rangeFetcher: async (context) => { - const [{ max }] = await dbRead.$queryRaw<{ max: number }[]>( - Prisma.sql`SELECT MAX(id) "max" FROM "Image";` - ); - return { ...context, end: max }; - }, - processor: async ({ start, end, cancelFns }) => { - const { cancel, result } = await pgDbWrite.cancellableQuery<{ - imageId: number; - tagId: number; - automated: boolean; - confidence: number | null; - disabledAt: string | null; - needsReview: boolean; - source: TagSource; - }>(Prisma.sql` - select insert_tag_on_image("imageId", "tagId", "source", "confidence", "automated", case when "disabledAt" is not null then true else false end, "needsReview") - from "TagsOnImage" - where "imageId" in (SELECT id from "Image" where id between ${start} and ${end}) - `); - - cancelFns.push(cancel); - await result(); - console.log(`migration: ${start} - ${end}`); - }, - }); - - console.timeEnd('MIGRATION_TIMER'); -}); diff --git a/src/pages/api/mod/mod-rules.ts b/src/pages/api/mod/mod-rules.ts index cd798c61d9..807630705a 100644 --- a/src/pages/api/mod/mod-rules.ts +++ b/src/pages/api/mod/mod-rules.ts @@ -3,13 +3,14 @@ import { z } from 'zod'; import { dbWrite } from '~/server/db/client'; import { bustImageModRulesCache } from '~/server/services/image.service'; import { bustModelModRulesCache } from '~/server/services/model.service'; -import { handleEndpointError, ModEndpoint } from '~/server/utils/endpoint-helpers'; +import { handleEndpointError, WebhookEndpoint } from '~/server/utils/endpoint-helpers'; import { handleLogError } from '~/server/utils/errorHandling'; import { EntityType, ModerationRuleAction } from '~/shared/utils/prisma/enums'; const payloadSchema = z.object({ id: z.number(), definition: z.object({}).passthrough(), + userId: z.number(), action: z.nativeEnum(ModerationRuleAction), entityType: z.enum(['Model', 'Image']), enabled: z.boolean().optional().default(true), @@ -17,28 +18,26 @@ const payloadSchema = z.object({ reason: z.string().optional(), }); -const deleteQuerySchema = z.object({ - id: z.coerce.number(), -}); +const deleteQuerySchema = z.object({ id: z.coerce.number() }); -export default ModEndpoint( - async function handler(req, res) { - try { - switch (req.method) { - case 'POST': - return upsertModRule(req, res); - case 'DELETE': - return deleteModRule(req, res); - default: { - return res.status(405).json({ error: 'Method Not Allowed' }); - } +export default WebhookEndpoint(async function handler(req, res) { + if (req.method && ['POST', 'DELETE'].includes(req.method)) + return res.status(405).json({ error: 'Method Not Allowed' }); + + try { + switch (req.method) { + case 'POST': + return upsertModRule(req, res); + case 'DELETE': + return deleteModRule(req, res); + default: { + return res.status(405).json({ error: 'Method Not Allowed' }); } - } catch (error) { - return handleEndpointError(res, error); } - }, - ['POST', 'DELETE'] -); + } catch (error) { + return handleEndpointError(res, error); + } +}); async function upsertModRule(req: NextApiRequest, res: NextApiResponse) { if (req.body.id) { @@ -47,7 +46,7 @@ async function upsertModRule(req: NextApiRequest, res: NextApiResponse) { return res.status(400).json({ error: 'Bad Request', details: schemaResult.error.format() }); try { - const { id, ...data } = schemaResult.data; + const { id, userId, ...data } = schemaResult.data; await dbWrite.moderationRule.update({ where: { id }, data }); } catch (error) { return res.status(500).json({ error: 'Could not update rule', details: error }); @@ -58,8 +57,8 @@ async function upsertModRule(req: NextApiRequest, res: NextApiResponse) { return res.status(400).json({ error: 'Bad Request', details: schemaResult.error.format }); try { - const data = schemaResult.data; - await dbWrite.moderationRule.create({ data }); + const { userId: createdById, ...data } = schemaResult.data; + await dbWrite.moderationRule.create({ data: { ...data, createdById } }); } catch (error) { return res.status(500).json({ error: 'Could not create rule', details: error }); } diff --git a/src/pages/api/mod/withdraw-from-bank.ts b/src/pages/api/mod/withdraw-from-bank.ts new file mode 100644 index 0000000000..9098bfdf20 --- /dev/null +++ b/src/pages/api/mod/withdraw-from-bank.ts @@ -0,0 +1,37 @@ +import { NextApiRequest, NextApiResponse } from 'next'; +import { z } from 'zod'; +import { WebhookEndpoint } from '~/server/utils/endpoint-helpers'; +import { getMonthAccount } from '~/server/services/creator-program.service'; +import { createBuzzTransaction } from '~/server/services/buzz.service'; +import { TransactionType } from '~/server/schema/buzz.schema'; + +const schema = z.object({ + amount: z.coerce.number(), + userId: z.coerce.number().optional(), +}); + +export default WebhookEndpoint(async (req: NextApiRequest, res: NextApiResponse) => { + const monthAccount = getMonthAccount(); + const { userId, amount } = schema.parse(req.body); + + try { + const { transactionId } = await createBuzzTransaction({ + fromAccountId: monthAccount, + fromAccountType: 'creatorprogrambank', + toAccountId: userId ?? -1, + toAccountType: 'user', + amount, + type: TransactionType.Withdrawal, + description: `ADMIN WITHDRAWAL FROM BANK.`, + }); + + return res.status(200).json({ + transactionId, + amount, + }); + } catch (error) { + return res.status(500).json({ + error, + }); + } +}); diff --git a/src/server/db/db-helpers.ts b/src/server/db/db-helpers.ts index b2c624d265..5d7b47f289 100644 --- a/src/server/db/db-helpers.ts +++ b/src/server/db/db-helpers.ts @@ -129,6 +129,7 @@ type LaggingType = | 'postImages' | 'article'; +// TODO move these functions so we dont import redis when getting pgdb export async function getDbWithoutLag(type: LaggingType, id?: number) { if (env.REPLICATION_LAG_DELAY <= 0 || !id) return dbRead; const value = await redis.get(`${REDIS_KEYS.LAG_HELPER}:${type}:${id}`); diff --git a/src/server/email/client.ts b/src/server/email/client.ts index e663f709d7..48f772c5b6 100644 --- a/src/server/email/client.ts +++ b/src/server/email/client.ts @@ -7,6 +7,8 @@ const shouldConnect = const client = shouldConnect ? nodemailer.createTransport({ pool: true, + maxConnections: 30, // default 5 + maxMessages: 600, // default 100 host: env.EMAIL_HOST, port: env.EMAIL_PORT, secure: env.EMAIL_SECURE, diff --git a/src/shared/utils/prisma/models.ts b/src/shared/utils/prisma/models.ts index 310c855149..c7eeb3839c 100644 --- a/src/shared/utils/prisma/models.ts +++ b/src/shared/utils/prisma/models.ts @@ -407,6 +407,7 @@ export interface User { cashWithdrawals?: CashWithdrawal[]; bids?: Bid[]; recurringBids?: BidRecurring[]; + moderationRules?: ModerationRule[]; } export interface CustomerSubscription { @@ -2602,6 +2603,8 @@ export interface ModerationRule { enabled: boolean; order: number | null; reason: string | null; + createdById: number; + createdBy?: User; } export interface QuestionRank {