Skip to content

Commit

Permalink
feat: profile reports on mod (#4643)
Browse files Browse the repository at this point in the history
  • Loading branch information
bigint committed Feb 14, 2024
2 parents 9deff71 + 496bcec commit 68ff60b
Show file tree
Hide file tree
Showing 15 changed files with 310 additions and 69 deletions.
12 changes: 12 additions & 0 deletions apps/api/src/db/clickhouse.sql
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,25 @@ import { Errors } from '@hey/data/errors';
import logger from '@hey/lib/logger';
import createClickhouseClient from 'src/lib/createClickhouseClient';

const heyTrustedReports = async (
const heyReports = async (
limit: number,
offset: number
offset: number,
isTrusted = false
): Promise<any[]> => {
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 trusted_reports
FROM ${table}
WHERE resolved = 0
GROUP BY publication_id
ORDER BY count DESC
Expand All @@ -30,12 +32,12 @@ const heyTrustedReports = async (
const result = await rows.json<Array<{ id: string }>>();

const ids = result.map((r) => r.id);
logger.info(`[Hey] Trusted reports: ${ids.length} ids`);
logger.info(`[Hey] Reports: ${ids.length} ids`);

return ids;
} catch {
return [];
}
};

export default heyTrustedReports;
export default heyReports;
Original file line number Diff line number Diff line change
Expand Up @@ -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) => {
Expand All @@ -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 });
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<Array<{ count: string; reason: string }>>();
logger.info(`Report details fetched for ${id}`);

return res.status(200).json({
result: result.map((row) => ({
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
import type { Handler } from 'express';

import catchedError from 'src/lib/catchedError';
import heyTrustedReports from 'src/lib/feeds/providers/hey/algorithms/heyTrustedReports';
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 heyTrustedReports(limit, offset), success: true });
return res.status(200).json({
ids: await heyReports(limit, offset, isTrusted),
success: true
});
} catch (error) {
return catchedError(res, error);
}
Expand Down
79 changes: 79 additions & 0 deletions apps/api/src/routes/misc/report.ts
Original file line number Diff line number Diff line change
@@ -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<Array<any>>();

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);
}
};
24 changes: 14 additions & 10 deletions apps/web/src/components/Mod/Actions.tsx
Original file line number Diff line number Diff line change
@@ -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<ActionsProps> = ({
hideTrustedReport = false,
publicationId
}) => {
const Actions: FC<ActionsProps> = ({ 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;
}
Expand All @@ -30,18 +31,21 @@ const Actions: FC<ActionsProps> = ({
<div className="m-5 space-y-2">
<b>Gardener actions</b>
<GardenerActions
ableToRemoveReport={hideTrustedReport}
className="mt-3 max-w-md"
publicationId={publicationId}
setExpanded={setExpanded}
type={type}
/>
</div>
{hideTrustedReport && (
<TrustedReportDetails publicationId={publicationId} />
{(isTrustedReport || isNormalReport) && (
<ReportDetails
isTrustedReport={isTrustedReport}
publicationId={publicationId}
/>
)}
</>
)}
{trusted && !hideTrustedReport && (
{trusted && !isTrustedReport && !isNormalReport && (
<>
<div className="divider" />
<div className="m-5">
Expand Down
7 changes: 7 additions & 0 deletions apps/web/src/components/Mod/FeedType.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { Dispatch, FC, SetStateAction } from 'react';

import {
ClockIcon,
FlagIcon,
ShieldCheckIcon,
UsersIcon
} from '@heroicons/react/24/outline';
Expand All @@ -28,6 +29,12 @@ const FeedType: FC<FeedTypeProps> = ({ feedType, setFeedType }) => {
name="Trusted Reports"
onClick={() => setFeedType(ModFeedType.TRUSTED_REPORTS)}
/>
<TabButton
active={feedType === ModFeedType.REPORTS}
icon={<FlagIcon className="size-4" />}
name="Reports"
onClick={() => setFeedType(ModFeedType.REPORTS)}
/>
<TabButton
active={feedType === ModFeedType.PROFILES}
icon={<UsersIcon className="size-4" />}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<TrustedReportDetailsProps> = ({
const ReportDetails: FC<ReportDetailsProps> = ({
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 {
Expand Down Expand Up @@ -51,4 +51,4 @@ const TrustedReportDetails: FC<TrustedReportDetailsProps> = ({
);
};

export default TrustedReportDetails;
export default ReportDetails;

1 comment on commit 68ff60b

@vercel
Copy link

@vercel vercel bot commented on 68ff60b Feb 14, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

web – ./apps/web

web-heyxyz.vercel.app
hey.xyz
heyxyz.vercel.app
web-git-main-heyxyz.vercel.app

Please sign in to comment.