Add community content reports, map bans, and appeals
This commit is contained in:
@@ -6,6 +6,35 @@ export type Json =
|
||||
| { [key: string]: Json | undefined }
|
||||
| Json[];
|
||||
|
||||
type MapModerationFields = {
|
||||
report_strikes: number;
|
||||
report_cycle: number;
|
||||
banned_at: string | null;
|
||||
ban_reason: string | null;
|
||||
};
|
||||
type ReportModerationFields = {
|
||||
category: string;
|
||||
revision_id: string | null;
|
||||
panorama_id: string | null;
|
||||
evidence_url: string | null;
|
||||
report_cycle: number;
|
||||
reviewer_id: string | null;
|
||||
reviewed_at: string | null;
|
||||
review_reason: string | null;
|
||||
};
|
||||
type AppealRecord = {
|
||||
id: string;
|
||||
dataset_id: string;
|
||||
owner_id: string;
|
||||
report_cycle: number;
|
||||
reason: string;
|
||||
state: string;
|
||||
created_at: string;
|
||||
reviewer_id: string | null;
|
||||
reviewed_at: string | null;
|
||||
review_reason: string | null;
|
||||
};
|
||||
|
||||
export type Database = {
|
||||
// Allows to automatically instantiate createClient with right options
|
||||
// instead of createClient<Database, { PostgrestVersion: 'XX' }>(URL, KEY)
|
||||
@@ -14,6 +43,16 @@ export type Database = {
|
||||
};
|
||||
public: {
|
||||
Tables: {
|
||||
map_appeals: {
|
||||
Row: AppealRecord;
|
||||
Insert: Partial<AppealRecord> &
|
||||
Pick<
|
||||
AppealRecord,
|
||||
"dataset_id" | "owner_id" | "report_cycle" | "reason"
|
||||
>;
|
||||
Update: Partial<AppealRecord>;
|
||||
Relationships: [];
|
||||
};
|
||||
catalog_servers: {
|
||||
Row: {
|
||||
address: string | null;
|
||||
@@ -89,7 +128,7 @@ export type Database = {
|
||||
];
|
||||
};
|
||||
datasets: {
|
||||
Row: {
|
||||
Row: MapModerationFields & {
|
||||
allow_contributions: boolean;
|
||||
bounds: Json;
|
||||
contribution_count: number;
|
||||
@@ -112,7 +151,7 @@ export type Database = {
|
||||
update_state: string | null;
|
||||
updated_at: string;
|
||||
};
|
||||
Insert: {
|
||||
Insert: Partial<MapModerationFields> & {
|
||||
allow_contributions?: boolean;
|
||||
bounds: Json;
|
||||
contribution_count?: number;
|
||||
@@ -135,7 +174,7 @@ export type Database = {
|
||||
update_state?: string | null;
|
||||
updated_at?: string;
|
||||
};
|
||||
Update: {
|
||||
Update: Partial<MapModerationFields> & {
|
||||
allow_contributions?: boolean;
|
||||
bounds?: Json;
|
||||
contribution_count?: number;
|
||||
@@ -322,7 +361,7 @@ export type Database = {
|
||||
Relationships: [];
|
||||
};
|
||||
reports: {
|
||||
Row: {
|
||||
Row: ReportModerationFields & {
|
||||
created_at: string;
|
||||
dataset_id: string;
|
||||
id: string;
|
||||
@@ -330,7 +369,7 @@ export type Database = {
|
||||
reason: string;
|
||||
state: string;
|
||||
};
|
||||
Insert: {
|
||||
Insert: Partial<ReportModerationFields> & {
|
||||
created_at?: string;
|
||||
dataset_id: string;
|
||||
id?: string;
|
||||
@@ -338,7 +377,7 @@ export type Database = {
|
||||
reason: string;
|
||||
state?: string;
|
||||
};
|
||||
Update: {
|
||||
Update: Partial<ReportModerationFields> & {
|
||||
created_at?: string;
|
||||
dataset_id?: string;
|
||||
id?: string;
|
||||
@@ -481,6 +520,40 @@ export type Database = {
|
||||
[_ in never]: never;
|
||||
};
|
||||
Functions: {
|
||||
submit_content_report: {
|
||||
Args: {
|
||||
p_dataset: string;
|
||||
p_owner: string;
|
||||
p_revision: string;
|
||||
p_panorama: string | null;
|
||||
p_category: string;
|
||||
p_reason: string;
|
||||
p_evidence?: string | null;
|
||||
};
|
||||
Returns: string;
|
||||
};
|
||||
review_content_report: {
|
||||
Args: {
|
||||
p_report: string;
|
||||
p_reviewer: string;
|
||||
p_decision: string;
|
||||
p_reason: string;
|
||||
};
|
||||
Returns: undefined;
|
||||
};
|
||||
appeal_map_ban: {
|
||||
Args: { p_dataset: string; p_owner: string; p_reason: string };
|
||||
Returns: string;
|
||||
};
|
||||
review_map_appeal: {
|
||||
Args: {
|
||||
p_appeal: string;
|
||||
p_reviewer: string;
|
||||
p_decision: string;
|
||||
p_reason: string;
|
||||
};
|
||||
Returns: undefined;
|
||||
};
|
||||
refresh_map: {
|
||||
Args: { p_dataset: string; p_owner: string; p_source: string };
|
||||
Returns: undefined;
|
||||
|
||||
52
shared/moderation.ts
Normal file
52
shared/moderation.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
import { z } from "zod";
|
||||
|
||||
export const reportCategories = {
|
||||
inappropriate: "Inappropriate or offensive content",
|
||||
privacy: "Personal or private information",
|
||||
wrong_world: "Wrong world or misleading map",
|
||||
broken: "Broken panorama or map",
|
||||
spam: "Spam or advertising",
|
||||
other: "Other",
|
||||
} as const;
|
||||
export const reportSchema = z
|
||||
.object({
|
||||
datasetId: z.string().uuid().optional(),
|
||||
sessionId: z.string().uuid().optional(),
|
||||
round: z.number().int().min(0).max(4).optional(),
|
||||
target: z.enum(["map", "panorama"]),
|
||||
category: z.enum(
|
||||
Object.keys(reportCategories) as [
|
||||
keyof typeof reportCategories,
|
||||
...Array<keyof typeof reportCategories>,
|
||||
],
|
||||
),
|
||||
reason: z.string().trim().min(10).max(2000),
|
||||
evidenceUrl: z
|
||||
.string()
|
||||
.trim()
|
||||
.url()
|
||||
.max(1000)
|
||||
.refine(
|
||||
(url) => /^https?:\/\//i.test(url),
|
||||
"Use an HTTP or HTTPS evidence link",
|
||||
)
|
||||
.optional(),
|
||||
})
|
||||
.superRefine((report, context) => {
|
||||
if (Boolean(report.datasetId) === Boolean(report.sessionId))
|
||||
context.addIssue({
|
||||
code: "custom",
|
||||
message: "Choose a map or a game session",
|
||||
});
|
||||
if (
|
||||
report.target === "panorama" &&
|
||||
(!report.sessionId || report.round === undefined)
|
||||
)
|
||||
context.addIssue({
|
||||
code: "custom",
|
||||
message: "Choose a panorama from your game",
|
||||
});
|
||||
});
|
||||
|
||||
export const contributionCutoff = (now = Date.now()) =>
|
||||
new Date(now - 5 * 24 * 60 * 60 * 1000).toISOString();
|
||||
Reference in New Issue
Block a user