From 26c0aa17ff3bbdc0c6458c7a3be381023858c2d2 Mon Sep 17 00:00:00 2001 From: bigint <69431456+bigint@users.noreply.github.com> Date: Tue, 13 Feb 2024 23:26:23 +0530 Subject: [PATCH 1/5] chore: rename to trusted report feed --- .../Mod/{ReportFeed.tsx => TrustedReportFeed.tsx} | 13 ++++++++----- apps/web/src/components/Mod/index.tsx | 4 ++-- 2 files changed, 10 insertions(+), 7 deletions(-) rename apps/web/src/components/Mod/{ReportFeed.tsx => TrustedReportFeed.tsx} (90%) diff --git a/apps/web/src/components/Mod/ReportFeed.tsx b/apps/web/src/components/Mod/TrustedReportFeed.tsx similarity index 90% rename from apps/web/src/components/Mod/ReportFeed.tsx rename to apps/web/src/components/Mod/TrustedReportFeed.tsx index da70a90c9807..a0903e13c632 100644 --- a/apps/web/src/components/Mod/ReportFeed.tsx +++ b/apps/web/src/components/Mod/TrustedReportFeed.tsx @@ -14,13 +14,16 @@ import { useInView } from 'react-cool-inview'; import Actions from './Actions'; -const ReportFeed: FC = () => { +const TrustedReportFeed: FC = () => { const [displayedPublications, setDisplayedPublications] = useState([]); const limit = LimitType.TwentyFive; const offset = displayedPublications.length; - const getReportFeed = async (limit: null | number, offset: null | number) => { + const getTrustedReportFeed = async ( + limit: null | number, + offset: null | number + ) => { try { const response = await axios.get(`${HEY_API_URL}/trusted/publications`, { params: { limit, offset } @@ -37,8 +40,8 @@ const ReportFeed: FC = () => { error: algoError, isLoading: algoLoading } = useQuery({ - queryFn: async () => await getReportFeed(25, offset), - queryKey: ['getReportFeed', 25, offset] + queryFn: async () => await getTrustedReportFeed(25, offset), + queryKey: ['getTrustedReportFeed', 25, offset] }); const request: PublicationsRequest = { @@ -105,4 +108,4 @@ const ReportFeed: FC = () => { ); }; -export default ReportFeed; +export default TrustedReportFeed; diff --git a/apps/web/src/components/Mod/index.tsx b/apps/web/src/components/Mod/index.tsx index 95a7c37f25ef..b666d187006c 100644 --- a/apps/web/src/components/Mod/index.tsx +++ b/apps/web/src/components/Mod/index.tsx @@ -29,7 +29,7 @@ import { useFeatureFlagsStore } from 'src/store/persisted/useFeatureFlagsStore'; import FeedType from './FeedType'; import LatestFeed from './LatestFeed'; -import ReportFeed from './ReportFeed'; +import TrustedReportFeed from './TrustedReportFeed'; const FILTER_APPS = knownApps; @@ -103,7 +103,7 @@ const Mod: NextPage = () => { setRefreshing={setRefreshing} /> )} - {feedType === ModFeedType.TRUSTED_REPORTS && } + {feedType === ModFeedType.TRUSTED_REPORTS && } {feedType === ModFeedType.PROFILES && } From 7fd8ad70a9c9f15ab53648d7f765600073671005 Mon Sep 17 00:00:00 2001 From: bigint <69431456+bigint@users.noreply.github.com> Date: Tue, 13 Feb 2024 23:38:08 +0530 Subject: [PATCH 2/5] feat: add normal reports --- apps/api/src/db/clickhouse.sql | 12 ++ .../providers/hey/algorithms/heyReports.ts | 38 ++++++ apps/api/src/routes/misc/report.ts | 79 +++++++++++++ apps/api/src/routes/mod/reports.ts | 17 +++ apps/web/src/components/Mod/FeedType.tsx | 7 ++ ...{TrustedReportFeed.tsx => ReportsFeed.tsx} | 4 +- .../src/components/Mod/TrustedReportsFeed.tsx | 111 ++++++++++++++++++ apps/web/src/components/Mod/index.tsx | 11 +- packages/data/enums.ts | 1 + 9 files changed, 275 insertions(+), 5 deletions(-) create mode 100644 apps/api/src/lib/feeds/providers/hey/algorithms/heyReports.ts create mode 100644 apps/api/src/routes/misc/report.ts create mode 100644 apps/api/src/routes/mod/reports.ts rename apps/web/src/components/Mod/{TrustedReportFeed.tsx => ReportsFeed.tsx} (97%) create mode 100644 apps/web/src/components/Mod/TrustedReportsFeed.tsx diff --git a/apps/api/src/db/clickhouse.sql b/apps/api/src/db/clickhouse.sql index f69062474fd3..5f804b9fdd75 100644 --- a/apps/api/src/db/clickhouse.sql +++ b/apps/api/src/db/clickhouse.sql @@ -44,6 +44,18 @@ CREATE TABLE "trusted_reports" ( actor LowCardinality(String), publication_id LowCardinality(String), reason String, + resolved Boolean DEFAULT 0, + created DateTime DEFAULT now() +) ENGINE = MergeTree +ORDER BY created; + +-- Reports +CREATE TABLE "reports" ( + id UUID DEFAULT generateUUIDv4(), + actor LowCardinality(String), + publication_id LowCardinality(String), + reason String, + resolved Boolean DEFAULT 0, created DateTime DEFAULT now() ) ENGINE = MergeTree ORDER BY created; diff --git a/apps/api/src/lib/feeds/providers/hey/algorithms/heyReports.ts b/apps/api/src/lib/feeds/providers/hey/algorithms/heyReports.ts new file mode 100644 index 000000000000..d6dd12b29602 --- /dev/null +++ b/apps/api/src/lib/feeds/providers/hey/algorithms/heyReports.ts @@ -0,0 +1,38 @@ +import { Errors } from '@hey/data/errors'; +import logger from '@hey/lib/logger'; +import createClickhouseClient from 'src/lib/createClickhouseClient'; + +const heyReports = async (limit: number, offset: number): Promise => { + if (limit > 500) { + throw new Error(Errors.Limit500); + } + + try { + const client = createClickhouseClient(); + const rows = await client.query({ + format: 'JSONEachRow', + query: ` + SELECT + publication_id AS id, + count(*) as count + FROM reports + WHERE resolved = 0 + GROUP BY publication_id + ORDER BY count DESC + LIMIT ${limit} + OFFSET ${offset}; + ` + }); + + const result = await rows.json>(); + + const ids = result.map((r) => r.id); + logger.info(`[Hey] Reports: ${ids.length} ids`); + + return ids; + } catch { + return []; + } +}; + +export default heyReports; diff --git a/apps/api/src/routes/misc/report.ts b/apps/api/src/routes/misc/report.ts new file mode 100644 index 000000000000..b9af4a97c753 --- /dev/null +++ b/apps/api/src/routes/misc/report.ts @@ -0,0 +1,79 @@ +import type { Handler } from 'express'; + +import logger from '@hey/lib/logger'; +import parseJwt from '@hey/lib/parseJwt'; +import catchedError from 'src/lib/catchedError'; +import createClickhouseClient from 'src/lib/createClickhouseClient'; +import validateLensAccount from 'src/lib/middlewares/validateLensAccount'; +import { invalidBody, noBody, notAllowed } from 'src/lib/responses'; +import { object, string } from 'zod'; + +type ExtensionRequest = { + id: string; + reason: string; +}; + +const validationSchema = object({ + id: string(), + reason: string() +}); + +export const post: Handler = async (req, res) => { + const { body } = req; + + if (!body) { + return noBody(res); + } + + const accessToken = req.headers['x-access-token'] as string; + const validation = validationSchema.safeParse(body); + + if (!validation.success) { + return invalidBody(res); + } + + if (!(await validateLensAccount(req))) { + return notAllowed(res); + } + + const { id, reason } = body as ExtensionRequest; + + try { + const payload = parseJwt(accessToken); + const actor = payload.id; + + const client = createClickhouseClient(); + + // Do not allow to report the same publication twice + const rows = await client.query({ + format: 'JSONEachRow', + query: ` + SELECT * FROM reports + WHERE publication_id = '${id}' + AND actor = '${actor}' + LIMIT 1; + ` + }); + + const reports = await rows.json>(); + + if (reports.length > 0) { + return res.status(200).json({ + message: 'You already reported this publication!', + success: false + }); + } + + const result = await client.insert({ + format: 'JSONEachRow', + table: 'reports', + values: [{ actor, publication_id: id, reason }] + }); + + logger.info(`Reported ${id} by profile: ${actor}`); + + return res.status(200).json({ id: result.query_id, success: true }); + } catch (error) { + return catchedError(res, error); + } +}; diff --git a/apps/api/src/routes/mod/reports.ts b/apps/api/src/routes/mod/reports.ts new file mode 100644 index 000000000000..8173cef470d4 --- /dev/null +++ b/apps/api/src/routes/mod/reports.ts @@ -0,0 +1,17 @@ +import type { Handler } from 'express'; + +import catchedError from 'src/lib/catchedError'; +import heyReports from 'src/lib/feeds/providers/hey/algorithms/heyReports'; + +export const get: Handler = async (req, res) => { + const limit = (parseInt(req.query?.limit as string) || 50) as number; + const offset = (parseInt(req.query?.offset as string) || 0) as number; + + try { + return res + .status(200) + .json({ ids: await heyReports(limit, offset), success: true }); + } catch (error) { + return catchedError(res, error); + } +}; diff --git a/apps/web/src/components/Mod/FeedType.tsx b/apps/web/src/components/Mod/FeedType.tsx index be3ad7a1fa74..6eb3d71d8eaf 100644 --- a/apps/web/src/components/Mod/FeedType.tsx +++ b/apps/web/src/components/Mod/FeedType.tsx @@ -2,6 +2,7 @@ import type { Dispatch, FC, SetStateAction } from 'react'; import { ClockIcon, + FlagIcon, ShieldCheckIcon, UsersIcon } from '@heroicons/react/24/outline'; @@ -28,6 +29,12 @@ const FeedType: FC = ({ feedType, setFeedType }) => { name="Trusted Reports" onClick={() => setFeedType(ModFeedType.TRUSTED_REPORTS)} /> + } + name="Reports" + onClick={() => setFeedType(ModFeedType.REPORTS)} + /> } diff --git a/apps/web/src/components/Mod/TrustedReportFeed.tsx b/apps/web/src/components/Mod/ReportsFeed.tsx similarity index 97% rename from apps/web/src/components/Mod/TrustedReportFeed.tsx rename to apps/web/src/components/Mod/ReportsFeed.tsx index a0903e13c632..a9a69ce20967 100644 --- a/apps/web/src/components/Mod/TrustedReportFeed.tsx +++ b/apps/web/src/components/Mod/ReportsFeed.tsx @@ -14,7 +14,7 @@ import { useInView } from 'react-cool-inview'; import Actions from './Actions'; -const TrustedReportFeed: FC = () => { +const ReportsFeed: FC = () => { const [displayedPublications, setDisplayedPublications] = useState([]); const limit = LimitType.TwentyFive; @@ -108,4 +108,4 @@ const TrustedReportFeed: FC = () => { ); }; -export default TrustedReportFeed; +export default ReportsFeed; diff --git a/apps/web/src/components/Mod/TrustedReportsFeed.tsx b/apps/web/src/components/Mod/TrustedReportsFeed.tsx new file mode 100644 index 000000000000..8502b80bbede --- /dev/null +++ b/apps/web/src/components/Mod/TrustedReportsFeed.tsx @@ -0,0 +1,111 @@ +import type { AnyPublication, PublicationsRequest } from '@hey/lens'; +import type { FC } from 'react'; + +import SinglePublication from '@components/Publication/SinglePublication'; +import PublicationsShimmer from '@components/Shared/Shimmer/PublicationsShimmer'; +import { SparklesIcon } from '@heroicons/react/24/outline'; +import { HEY_API_URL } from '@hey/data/constants'; +import { LimitType, usePublicationsQuery } from '@hey/lens'; +import { Card, EmptyState, ErrorMessage } from '@hey/ui'; +import { useQuery } from '@tanstack/react-query'; +import axios from 'axios'; +import { useState } from 'react'; +import { useInView } from 'react-cool-inview'; + +import Actions from './Actions'; + +const TrustedReportsFeed: FC = () => { + const [displayedPublications, setDisplayedPublications] = useState([]); + + const limit = LimitType.TwentyFive; + const offset = displayedPublications.length; + + const getTrustedReportFeed = async ( + limit: null | number, + offset: null | number + ) => { + try { + const response = await axios.get(`${HEY_API_URL}/trusted/publications`, { + params: { limit, offset } + }); + + return response.data.success ? response.data.ids : []; + } catch { + return []; + } + }; + + const { + data: publicationIds, + error: algoError, + isLoading: algoLoading + } = useQuery({ + queryFn: async () => await getTrustedReportFeed(25, offset), + queryKey: ['getTrustedReportFeed', 25, offset] + }); + + const request: PublicationsRequest = { + limit, + where: { publicationIds } + }; + + const { data, error, loading } = usePublicationsQuery({ + fetchPolicy: 'no-cache', + skip: !publicationIds, + variables: { request } + }); + + const publications = [ + ...displayedPublications, + ...(data?.publications?.items || []) + ]; + + const { observe } = useInView({ + onChange: ({ inView }) => { + if (!inView) { + return; + } + + if (publications.length !== displayedPublications.length) { + setDisplayedPublications(publications); + } + } + }); + + if (publications.length === 0 && (algoLoading || loading)) { + return ; + } + + if (publications?.length === 0) { + return ( + } + message="No posts yet!" + /> + ); + } + + if (publications.length === 0 && (error || algoError)) { + return ; + } + + return ( +
+ {publications?.map((publication, index) => ( + + + + + ))} + +
+ ); +}; + +export default TrustedReportsFeed; diff --git a/apps/web/src/components/Mod/index.tsx b/apps/web/src/components/Mod/index.tsx index b666d187006c..8d041dcc0c70 100644 --- a/apps/web/src/components/Mod/index.tsx +++ b/apps/web/src/components/Mod/index.tsx @@ -29,7 +29,8 @@ import { useFeatureFlagsStore } from 'src/store/persisted/useFeatureFlagsStore'; import FeedType from './FeedType'; import LatestFeed from './LatestFeed'; -import TrustedReportFeed from './TrustedReportFeed'; +import ReportsFeed from './ReportsFeed'; +import TrustedReportsFeed from './TrustedReportsFeed'; const FILTER_APPS = knownApps; @@ -103,7 +104,8 @@ const Mod: NextPage = () => { setRefreshing={setRefreshing} /> )} - {feedType === ModFeedType.TRUSTED_REPORTS && } + {feedType === ModFeedType.TRUSTED_REPORTS && } + {feedType === ModFeedType.REPORTS && } {feedType === ModFeedType.PROFILES && } @@ -254,7 +256,10 @@ const Mod: NextPage = () => { )} {feedType === ModFeedType.TRUSTED_REPORTS && ( -
Take action on trusted profile reports
+
Take action on trusted profile reported publications
+ )} + {feedType === ModFeedType.REPORTS && ( +
Take action on profile reported publications
)} {feedType === ModFeedType.PROFILES &&
All the profiles
} diff --git a/packages/data/enums.ts b/packages/data/enums.ts index c37d6ad52395..f63e6d01ddc2 100644 --- a/packages/data/enums.ts +++ b/packages/data/enums.ts @@ -14,6 +14,7 @@ export enum HomeFeedType { export enum ModFeedType { LATEST = 'LATEST', PROFILES = 'PROFILES', + REPORTS = 'REPORTS', TRUSTED_REPORTS = 'TRUSTED_REPORTS' } From 62f6517a08b93fa58a97424680c001fed7e9a374 Mon Sep 17 00:00:00 2001 From: bigint <69431456+bigint@users.noreply.github.com> Date: Tue, 13 Feb 2024 23:42:05 +0530 Subject: [PATCH 3/5] feat: use report --- .../Shared/Modal/ReportPublication/index.tsx | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/apps/web/src/components/Shared/Modal/ReportPublication/index.tsx b/apps/web/src/components/Shared/Modal/ReportPublication/index.tsx index 6c4e1aed412b..db7b6505c4d1 100644 --- a/apps/web/src/components/Shared/Modal/ReportPublication/index.tsx +++ b/apps/web/src/components/Shared/Modal/ReportPublication/index.tsx @@ -3,6 +3,7 @@ import type { FC } from 'react'; import { PencilSquareIcon } from '@heroicons/react/24/outline'; import { CheckCircleIcon } from '@heroicons/react/24/solid'; import { Errors } from '@hey/data'; +import { HEY_API_URL } from '@hey/data/constants'; import { PUBLICATION } from '@hey/data/tracking'; import { useReportPublicationMutation } from '@hey/lens'; import stopEventPropagation from '@hey/lib/stopEventPropagation'; @@ -16,7 +17,9 @@ import { useZodForm } from '@hey/ui'; import errorToast from '@lib/errorToast'; +import getAuthApiHeaders from '@lib/getAuthApiHeaders'; import { Leafwatch } from '@lib/leafwatch'; +import axios from 'axios'; import { useState } from 'react'; import toast from 'react-hot-toast'; import { useProfileRestriction } from 'src/store/non-persisted/useProfileRestriction'; @@ -54,12 +57,21 @@ const ReportPublication: FC = ({ publicationId }) => { } }); + const reportPublicationOnHey = async (reason: string) => { + await axios.post( + `${HEY_API_URL}/misc/report`, + { id: publicationId, reason }, + { headers: getAuthApiHeaders() } + ); + }; + const reportPublication = async (additionalComments: null | string) => { if (isSuspended) { return toast.error(Errors.Suspended); } try { + await reportPublicationOnHey(subReason); return await createReport({ variables: { request: { From b5faaa74e546993d68dfb891c5d1aacda4c82db1 Mon Sep 17 00:00:00 2001 From: bigint <69431456+bigint@users.noreply.github.com> Date: Tue, 13 Feb 2024 23:44:33 +0530 Subject: [PATCH 4/5] fix: reports consume --- apps/web/src/components/Mod/ReportsFeed.tsx | 2 +- .../web/src/components/Shared/Modal/ReportPublication/index.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/web/src/components/Mod/ReportsFeed.tsx b/apps/web/src/components/Mod/ReportsFeed.tsx index a9a69ce20967..4d7b7f98d807 100644 --- a/apps/web/src/components/Mod/ReportsFeed.tsx +++ b/apps/web/src/components/Mod/ReportsFeed.tsx @@ -25,7 +25,7 @@ const ReportsFeed: FC = () => { offset: null | number ) => { try { - const response = await axios.get(`${HEY_API_URL}/trusted/publications`, { + const response = await axios.get(`${HEY_API_URL}/mod/reports`, { params: { limit, offset } }); diff --git a/apps/web/src/components/Shared/Modal/ReportPublication/index.tsx b/apps/web/src/components/Shared/Modal/ReportPublication/index.tsx index db7b6505c4d1..9be5675f3107 100644 --- a/apps/web/src/components/Shared/Modal/ReportPublication/index.tsx +++ b/apps/web/src/components/Shared/Modal/ReportPublication/index.tsx @@ -71,7 +71,7 @@ const ReportPublication: FC = ({ publicationId }) => { } try { - await reportPublicationOnHey(subReason); + reportPublicationOnHey(subReason); return await createReport({ variables: { request: { From 496bcecf20f3dfa084a755e64583a6f2dca6bfb8 Mon Sep 17 00:00:00 2001 From: bigint <69431456+bigint@users.noreply.github.com> Date: Wed, 14 Feb 2024 20:33:47 +0530 Subject: [PATCH 5/5] feat: update profile reports feed --- .../providers/hey/algorithms/heyReports.ts | 9 +++- .../hey/algorithms/heyTrustedReports.ts | 41 ------------------- .../{trusted => gardener}/removeReport.ts | 21 +++++----- .../{trusted => gardener}/reportDetails.ts | 19 ++++----- .../src/routes/{mod => gardener}/reports.ts | 10 +++-- apps/api/src/routes/trusted/publications.ts | 17 -------- apps/web/src/components/Mod/Actions.tsx | 24 ++++++----- ...tedReportDetails.tsx => ReportDetails.tsx} | 20 ++++----- apps/web/src/components/Mod/ReportsFeed.tsx | 14 +++---- .../src/components/Mod/TrustedReportsFeed.tsx | 10 +++-- .../Actions/GardenerActions/index.tsx | 25 ++++++----- 11 files changed, 83 insertions(+), 127 deletions(-) delete mode 100644 apps/api/src/lib/feeds/providers/hey/algorithms/heyTrustedReports.ts rename apps/api/src/routes/{trusted => gardener}/removeReport.ts (71%) rename apps/api/src/routes/{trusted => gardener}/reportDetails.ts (72%) rename apps/api/src/routes/{mod => gardener}/reports.ts (67%) delete mode 100644 apps/api/src/routes/trusted/publications.ts rename apps/web/src/components/Mod/{TrustedReportDetails.tsx => ReportDetails.tsx} (72%) diff --git a/apps/api/src/lib/feeds/providers/hey/algorithms/heyReports.ts b/apps/api/src/lib/feeds/providers/hey/algorithms/heyReports.ts index d6dd12b29602..3bc125a49f09 100644 --- a/apps/api/src/lib/feeds/providers/hey/algorithms/heyReports.ts +++ b/apps/api/src/lib/feeds/providers/hey/algorithms/heyReports.ts @@ -2,20 +2,25 @@ import { Errors } from '@hey/data/errors'; import logger from '@hey/lib/logger'; import createClickhouseClient from 'src/lib/createClickhouseClient'; -const heyReports = async (limit: number, offset: number): Promise => { +const heyReports = async ( + limit: number, + offset: number, + isTrusted = false +): Promise => { if (limit > 500) { throw new Error(Errors.Limit500); } try { const client = createClickhouseClient(); + const table = isTrusted ? 'trusted_reports' : 'reports'; const rows = await client.query({ format: 'JSONEachRow', query: ` SELECT publication_id AS id, count(*) as count - FROM reports + FROM ${table} WHERE resolved = 0 GROUP BY publication_id ORDER BY count DESC diff --git a/apps/api/src/lib/feeds/providers/hey/algorithms/heyTrustedReports.ts b/apps/api/src/lib/feeds/providers/hey/algorithms/heyTrustedReports.ts deleted file mode 100644 index 398cf258653f..000000000000 --- a/apps/api/src/lib/feeds/providers/hey/algorithms/heyTrustedReports.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { Errors } from '@hey/data/errors'; -import logger from '@hey/lib/logger'; -import createClickhouseClient from 'src/lib/createClickhouseClient'; - -const heyTrustedReports = async ( - limit: number, - offset: number -): Promise => { - if (limit > 500) { - throw new Error(Errors.Limit500); - } - - try { - const client = createClickhouseClient(); - const rows = await client.query({ - format: 'JSONEachRow', - query: ` - SELECT - publication_id AS id, - count(*) as count - FROM trusted_reports - WHERE resolved = 0 - GROUP BY publication_id - ORDER BY count DESC - LIMIT ${limit} - OFFSET ${offset}; - ` - }); - - const result = await rows.json>(); - - const ids = result.map((r) => r.id); - logger.info(`[Hey] Trusted reports: ${ids.length} ids`); - - return ids; - } catch { - return []; - } -}; - -export default heyTrustedReports; diff --git a/apps/api/src/routes/trusted/removeReport.ts b/apps/api/src/routes/gardener/removeReport.ts similarity index 71% rename from apps/api/src/routes/trusted/removeReport.ts rename to apps/api/src/routes/gardener/removeReport.ts index 644dd93573c9..0df7e23bdf19 100644 --- a/apps/api/src/routes/trusted/removeReport.ts +++ b/apps/api/src/routes/gardener/removeReport.ts @@ -10,11 +10,13 @@ import { boolean, object, string } from 'zod'; type ExtensionRequest = { id: string; looksGood: boolean; + trusted: boolean; }; const validationSchema = object({ id: string(), - looksGood: boolean() + looksGood: boolean(), + trusted: boolean() }); export const post: Handler = async (req, res) => { @@ -34,25 +36,24 @@ export const post: Handler = async (req, res) => { return notAllowed(res); } - const { id, looksGood } = body as ExtensionRequest; + const { id, looksGood, trusted } = body as ExtensionRequest; + const table = trusted ? 'trusted_reports' : 'reports'; try { const client = createClickhouseClient(); if (looksGood) { await client.command({ - query: `DELETE FROM trusted_reports WHERE publication_id = '${id}';` + query: `DELETE FROM ${table} WHERE publication_id = '${id}';` }); - logger.info('Deleted report from trusted reports'); + + logger.info('Deleted report from reports'); } else { await client.command({ - query: ` - ALTER TABLE trusted_reports - UPDATE resolved = 1 - WHERE publication_id = '${id}'; - ` + query: `ALTER TABLE ${table} UPDATE resolved = 1 WHERE publication_id = '${id}';` }); - logger.info('Marked trusted report as resolved'); + + logger.info('Marked report as resolved'); } return res.status(200).json({ success: true }); diff --git a/apps/api/src/routes/trusted/reportDetails.ts b/apps/api/src/routes/gardener/reportDetails.ts similarity index 72% rename from apps/api/src/routes/trusted/reportDetails.ts rename to apps/api/src/routes/gardener/reportDetails.ts index 4064edb735d4..214f2882090a 100644 --- a/apps/api/src/routes/trusted/reportDetails.ts +++ b/apps/api/src/routes/gardener/reportDetails.ts @@ -6,34 +6,29 @@ import createClickhouseClient from 'src/lib/createClickhouseClient'; import { noBody } from 'src/lib/responses'; export const get: Handler = async (req, res) => { - const { id } = req.query; + const { id, trusted } = req.query; if (!id) { return noBody(res); } + const table = trusted === 'true' ? 'trusted_reports' : 'reports'; + try { const client = createClickhouseClient(); const rows = await client.query({ format: 'JSONEachRow', query: ` - SELECT - reason, - count(*) as count - FROM trusted_reports + SELECT reason, count(*) as count + FROM ${table} WHERE publication_id = '${id}' GROUP BY reason ORDER BY count DESC; ` }); - const result = await rows.json< - Array<{ - count: string; - reason: string; - }> - >(); - logger.info(`Trusted report details fetched for ${id}`); + const result = await rows.json>(); + logger.info(`Report details fetched for ${id}`); return res.status(200).json({ result: result.map((row) => ({ diff --git a/apps/api/src/routes/mod/reports.ts b/apps/api/src/routes/gardener/reports.ts similarity index 67% rename from apps/api/src/routes/mod/reports.ts rename to apps/api/src/routes/gardener/reports.ts index 8173cef470d4..ef9e63a85538 100644 --- a/apps/api/src/routes/mod/reports.ts +++ b/apps/api/src/routes/gardener/reports.ts @@ -6,11 +6,15 @@ import heyReports from 'src/lib/feeds/providers/hey/algorithms/heyReports'; export const get: Handler = async (req, res) => { const limit = (parseInt(req.query?.limit as string) || 50) as number; const offset = (parseInt(req.query?.offset as string) || 0) as number; + const trusted = req.query?.trusted as string; + + const isTrusted = trusted === 'true'; try { - return res - .status(200) - .json({ ids: await heyReports(limit, offset), success: true }); + return res.status(200).json({ + ids: await heyReports(limit, offset, isTrusted), + success: true + }); } catch (error) { return catchedError(res, error); } diff --git a/apps/api/src/routes/trusted/publications.ts b/apps/api/src/routes/trusted/publications.ts deleted file mode 100644 index 0691c3ea6612..000000000000 --- a/apps/api/src/routes/trusted/publications.ts +++ /dev/null @@ -1,17 +0,0 @@ -import type { Handler } from 'express'; - -import catchedError from 'src/lib/catchedError'; -import heyTrustedReports from 'src/lib/feeds/providers/hey/algorithms/heyTrustedReports'; - -export const get: Handler = async (req, res) => { - const limit = (parseInt(req.query?.limit as string) || 50) as number; - const offset = (parseInt(req.query?.offset as string) || 0) as number; - - try { - return res - .status(200) - .json({ ids: await heyTrustedReports(limit, offset), success: true }); - } catch (error) { - return catchedError(res, error); - } -}; diff --git a/apps/web/src/components/Mod/Actions.tsx b/apps/web/src/components/Mod/Actions.tsx index 8dfff49b777a..bb3f1b21bd36 100644 --- a/apps/web/src/components/Mod/Actions.tsx +++ b/apps/web/src/components/Mod/Actions.tsx @@ -1,23 +1,24 @@ import GardenerActions from '@components/Publication/Actions/GardenerActions'; import TrustedProfilesActions from '@components/Publication/Actions/TrustedProfilesActions'; +import { ModFeedType } from '@hey/data/enums'; import { type FC, useState } from 'react'; import { useFeatureFlagsStore } from 'src/store/persisted/useFeatureFlagsStore'; -import TrustedReportDetails from './TrustedReportDetails'; +import ReportDetails from './ReportDetails'; interface ActionsProps { - hideTrustedReport?: boolean; publicationId: string; + type?: ModFeedType.REPORTS | ModFeedType.TRUSTED_REPORTS; } -const Actions: FC = ({ - hideTrustedReport = false, - publicationId -}) => { +const Actions: FC = ({ publicationId, type }) => { const [expanded, setExpanded] = useState(true); const trusted = useFeatureFlagsStore((state) => state.trusted); const gardenerMode = useFeatureFlagsStore((state) => state.gardenerMode); + const isTrustedReport = type === ModFeedType.TRUSTED_REPORTS; + const isNormalReport = type === ModFeedType.REPORTS; + if (!expanded) { return null; } @@ -30,18 +31,21 @@ const Actions: FC = ({
Gardener actions
- {hideTrustedReport && ( - + {(isTrustedReport || isNormalReport) && ( + )} )} - {trusted && !hideTrustedReport && ( + {trusted && !isTrustedReport && !isNormalReport && ( <>
diff --git a/apps/web/src/components/Mod/TrustedReportDetails.tsx b/apps/web/src/components/Mod/ReportDetails.tsx similarity index 72% rename from apps/web/src/components/Mod/TrustedReportDetails.tsx rename to apps/web/src/components/Mod/ReportDetails.tsx index 9eb407964b88..d3143575db11 100644 --- a/apps/web/src/components/Mod/TrustedReportDetails.tsx +++ b/apps/web/src/components/Mod/ReportDetails.tsx @@ -4,23 +4,23 @@ import { HEY_API_URL } from '@hey/data/constants'; import { useQuery } from '@tanstack/react-query'; import axios from 'axios'; -interface TrustedReportDetailsProps { +interface ReportDetailsProps { + isTrustedReport: boolean; publicationId: string; } -const TrustedReportDetails: FC = ({ +const ReportDetails: FC = ({ + isTrustedReport, publicationId }) => { const fetchTrustedReportDetails = async (): Promise< - { - count: number; - reason: string; - }[] + { count: number; reason: string }[] > => { try { - const response = await axios.get(`${HEY_API_URL}/trusted/reportDetails`, { - params: { id: publicationId } - }); + const response = await axios.get( + `${HEY_API_URL}/gardener/reportDetails`, + { params: { id: publicationId, trusted: isTrustedReport } } + ); return response.data.result; } catch { @@ -51,4 +51,4 @@ const TrustedReportDetails: FC = ({ ); }; -export default TrustedReportDetails; +export default ReportDetails; diff --git a/apps/web/src/components/Mod/ReportsFeed.tsx b/apps/web/src/components/Mod/ReportsFeed.tsx index 4d7b7f98d807..e20a067dd5d7 100644 --- a/apps/web/src/components/Mod/ReportsFeed.tsx +++ b/apps/web/src/components/Mod/ReportsFeed.tsx @@ -5,6 +5,7 @@ import SinglePublication from '@components/Publication/SinglePublication'; import PublicationsShimmer from '@components/Shared/Shimmer/PublicationsShimmer'; import { SparklesIcon } from '@heroicons/react/24/outline'; import { HEY_API_URL } from '@hey/data/constants'; +import { ModFeedType } from '@hey/data/enums'; import { LimitType, usePublicationsQuery } from '@hey/lens'; import { Card, EmptyState, ErrorMessage } from '@hey/ui'; import { useQuery } from '@tanstack/react-query'; @@ -20,12 +21,9 @@ const ReportsFeed: FC = () => { const limit = LimitType.TwentyFive; const offset = displayedPublications.length; - const getTrustedReportFeed = async ( - limit: null | number, - offset: null | number - ) => { + const getReportFeed = async (limit: null | number, offset: null | number) => { try { - const response = await axios.get(`${HEY_API_URL}/mod/reports`, { + const response = await axios.get(`${HEY_API_URL}/gardener/reports`, { params: { limit, offset } }); @@ -40,8 +38,8 @@ const ReportsFeed: FC = () => { error: algoError, isLoading: algoLoading } = useQuery({ - queryFn: async () => await getTrustedReportFeed(25, offset), - queryKey: ['getTrustedReportFeed', 25, offset] + queryFn: async () => await getReportFeed(25, offset), + queryKey: ['getReportFeed', 25, offset] }); const request: PublicationsRequest = { @@ -100,7 +98,7 @@ const ReportsFeed: FC = () => { showActions={false} showThread={false} /> - + ))} diff --git a/apps/web/src/components/Mod/TrustedReportsFeed.tsx b/apps/web/src/components/Mod/TrustedReportsFeed.tsx index 8502b80bbede..0613c7485a59 100644 --- a/apps/web/src/components/Mod/TrustedReportsFeed.tsx +++ b/apps/web/src/components/Mod/TrustedReportsFeed.tsx @@ -5,6 +5,7 @@ import SinglePublication from '@components/Publication/SinglePublication'; import PublicationsShimmer from '@components/Shared/Shimmer/PublicationsShimmer'; import { SparklesIcon } from '@heroicons/react/24/outline'; import { HEY_API_URL } from '@hey/data/constants'; +import { ModFeedType } from '@hey/data/enums'; import { LimitType, usePublicationsQuery } from '@hey/lens'; import { Card, EmptyState, ErrorMessage } from '@hey/ui'; import { useQuery } from '@tanstack/react-query'; @@ -25,8 +26,8 @@ const TrustedReportsFeed: FC = () => { offset: null | number ) => { try { - const response = await axios.get(`${HEY_API_URL}/trusted/publications`, { - params: { limit, offset } + const response = await axios.get(`${HEY_API_URL}/gardener/reports`, { + params: { limit, offset, trusted: true } }); return response.data.success ? response.data.ids : []; @@ -100,7 +101,10 @@ const TrustedReportsFeed: FC = () => { showActions={false} showThread={false} /> - + ))} diff --git a/apps/web/src/components/Publication/Actions/GardenerActions/index.tsx b/apps/web/src/components/Publication/Actions/GardenerActions/index.tsx index aa74b3d5a0bb..ea1e2ac18918 100644 --- a/apps/web/src/components/Publication/Actions/GardenerActions/index.tsx +++ b/apps/web/src/components/Publication/Actions/GardenerActions/index.tsx @@ -7,6 +7,7 @@ import { HandThumbUpIcon } from '@heroicons/react/24/outline'; import { HEY_API_URL } from '@hey/data/constants'; +import { ModFeedType } from '@hey/data/enums'; import { GARDENER } from '@hey/data/tracking'; import { PublicationReportingSpamSubreason, @@ -22,38 +23,40 @@ import { toast } from 'react-hot-toast'; import { useGlobalAlertStateStore } from 'src/store/non-persisted/useGlobalAlertStateStore'; interface GardenerActionsProps { - ableToRemoveReport?: boolean; className?: string; publicationId: string; setExpanded?: (expanded: boolean) => void; + type?: ModFeedType.REPORTS | ModFeedType.TRUSTED_REPORTS; } const GardenerActions: FC = ({ - ableToRemoveReport = false, className = '', publicationId, - setExpanded = () => {} + setExpanded = () => {}, + type }) => { const setShowGardenerActionsAlert = useGlobalAlertStateStore( (state) => state.setShowGardenerActionsAlert ); const [createReport, { loading }] = useReportPublicationMutation(); + const ableToRemoveReport = + type === ModFeedType.TRUSTED_REPORTS || type === ModFeedType.REPORTS; - const removeTrustedReport = (id: string, looksGood: boolean) => { + const removeReport = (id: string, looksGood: boolean) => { const removeReport = async () => { return await axios.post( - `${HEY_API_URL}/trusted/removeReport`, - { id, looksGood }, + `${HEY_API_URL}/gardener/removeReport`, + { id, looksGood, trusted: type === ModFeedType.TRUSTED_REPORTS }, { headers: getAuthApiHeaders() } ); }; toast.promise(removeReport(), { - error: 'Error removing trusted reports', - loading: 'Removing trusted reports...', + error: 'Error removing report', + loading: 'Removing report...', success: () => { setExpanded(false); - return 'Trusted reports removed successfully'; + return 'Report removed successfully'; } }); }; @@ -77,7 +80,7 @@ const GardenerActions: FC = ({ }; if (ableToRemoveReport) { - removeTrustedReport(publicationId, false); + removeReport(publicationId, false); } return await createReport({ @@ -168,7 +171,7 @@ const GardenerActions: FC = ({ {ableToRemoveReport && (