feat: user blocking and content reporting/flagging
- user_blocks table (composite PK), Block/Unblock button on profiles. Blocking severs any existing follow relationship both ways and prevents the blocked party from following, commenting on the blocker's recipes, or the blocker from seeing their comments. - reports table (recipe/comment/user targets, pending/reviewed/ dismissed status). ReportButton on comments, admin review queue at /admin/reports with dismiss/mark-reviewed actions, audit-logged. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,26 @@
|
||||
CREATE TYPE "public"."report_status" AS ENUM('pending', 'reviewed', 'dismissed');--> statement-breakpoint
|
||||
CREATE TYPE "public"."report_target_type" AS ENUM('recipe', 'comment', 'user');--> statement-breakpoint
|
||||
CREATE TABLE "user_blocks" (
|
||||
"blocker_id" text NOT NULL,
|
||||
"blocked_id" text NOT NULL,
|
||||
"created_at" timestamp DEFAULT now() NOT NULL,
|
||||
CONSTRAINT "user_blocks_blocker_id_blocked_id_pk" PRIMARY KEY("blocker_id","blocked_id")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "reports" (
|
||||
"id" text PRIMARY KEY NOT NULL,
|
||||
"reporter_id" text NOT NULL,
|
||||
"target_type" "report_target_type" NOT NULL,
|
||||
"target_id" text NOT NULL,
|
||||
"reason" text NOT NULL,
|
||||
"status" "report_status" DEFAULT 'pending' NOT NULL,
|
||||
"created_at" timestamp DEFAULT now() NOT NULL,
|
||||
"reviewed_by_id" text,
|
||||
"reviewed_at" timestamp
|
||||
);
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "user_blocks" ADD CONSTRAINT "user_blocks_blocker_id_users_id_fk" FOREIGN KEY ("blocker_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "user_blocks" ADD CONSTRAINT "user_blocks_blocked_id_users_id_fk" FOREIGN KEY ("blocked_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "reports" ADD CONSTRAINT "reports_reporter_id_users_id_fk" FOREIGN KEY ("reporter_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "reports" ADD CONSTRAINT "reports_reviewed_by_id_users_id_fk" FOREIGN KEY ("reviewed_by_id") REFERENCES "public"."users"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
|
||||
CREATE INDEX "reports_status_idx" ON "reports" USING btree ("status","created_at");
|
||||
File diff suppressed because it is too large
Load Diff
@@ -134,6 +134,13 @@
|
||||
"when": 1783108229117,
|
||||
"tag": "0018_mighty_butterfly",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 19,
|
||||
"version": "7",
|
||||
"when": 1783108786252,
|
||||
"tag": "0019_clumsy_leader",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
boolean,
|
||||
index,
|
||||
unique,
|
||||
pgEnum,
|
||||
} from "drizzle-orm/pg-core";
|
||||
import { relations } from "drizzle-orm";
|
||||
import { users, tierEnum, userRoleEnum } from "./users";
|
||||
@@ -73,3 +74,25 @@ export const invitesRelations = relations(invites, ({ one }) => ({
|
||||
createdBy: one(users, { fields: [invites.createdById], references: [users.id] }),
|
||||
usedBy: one(users, { fields: [invites.usedById], references: [users.id] }),
|
||||
}));
|
||||
|
||||
export const reportTargetTypeEnum = pgEnum("report_target_type", ["recipe", "comment", "user"]);
|
||||
export const reportStatusEnum = pgEnum("report_status", ["pending", "reviewed", "dismissed"]);
|
||||
|
||||
export const reports = pgTable("reports", {
|
||||
id: text("id").primaryKey(),
|
||||
reporterId: text("reporter_id").notNull().references(() => users.id, { onDelete: "cascade" }),
|
||||
targetType: reportTargetTypeEnum("target_type").notNull(),
|
||||
targetId: text("target_id").notNull(),
|
||||
reason: text("reason").notNull(),
|
||||
status: reportStatusEnum("status").notNull().default("pending"),
|
||||
createdAt: timestamp("created_at").notNull().defaultNow(),
|
||||
reviewedById: text("reviewed_by_id").references(() => users.id, { onDelete: "set null" }),
|
||||
reviewedAt: timestamp("reviewed_at"),
|
||||
}, (t) => [
|
||||
index("reports_status_idx").on(t.status, t.createdAt),
|
||||
]);
|
||||
|
||||
export const reportsRelations = relations(reports, ({ one }) => ({
|
||||
reporter: one(users, { fields: [reports.reporterId], references: [users.id] }),
|
||||
reviewedBy: one(users, { fields: [reports.reviewedById], references: [users.id] }),
|
||||
}));
|
||||
|
||||
@@ -76,6 +76,19 @@ export const userFollows = pgTable("user_follows", {
|
||||
primaryKey({ columns: [t.followerId, t.followingId] }),
|
||||
]);
|
||||
|
||||
export const userBlocks = pgTable("user_blocks", {
|
||||
blockerId: text("blocker_id").notNull().references(() => users.id, { onDelete: "cascade" }),
|
||||
blockedId: text("blocked_id").notNull().references(() => users.id, { onDelete: "cascade" }),
|
||||
createdAt: timestamp("created_at").notNull().defaultNow(),
|
||||
}, (t) => [
|
||||
primaryKey({ columns: [t.blockerId, t.blockedId] }),
|
||||
]);
|
||||
|
||||
export const userBlocksRelations = relations(userBlocks, ({ one }) => ({
|
||||
blocker: one(users, { fields: [userBlocks.blockerId], references: [users.id], relationName: "blocker" }),
|
||||
blocked: one(users, { fields: [userBlocks.blockedId], references: [users.id], relationName: "blocked" }),
|
||||
}));
|
||||
|
||||
export const apiKeys = pgTable("api_keys", {
|
||||
id: text("id").primaryKey(),
|
||||
userId: text("user_id").notNull().references(() => users.id, { onDelete: "cascade" }),
|
||||
|
||||
Reference in New Issue
Block a user