@bash-app/bash-common 30.229.0 → 30.231.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/__tests__/userReportTypes.test.d.ts +2 -0
- package/dist/__tests__/userReportTypes.test.d.ts.map +1 -0
- package/dist/__tests__/userReportTypes.test.js +81 -0
- package/dist/__tests__/userReportTypes.test.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/userReportTypes.d.ts +117 -0
- package/dist/userReportTypes.d.ts.map +1 -0
- package/dist/userReportTypes.js +99 -0
- package/dist/userReportTypes.js.map +1 -0
- package/package.json +1 -1
- package/prisma/schema.prisma +30 -0
- package/src/__tests__/userReportTypes.test.ts +103 -0
- package/src/index.ts +1 -0
- package/src/userReportTypes.ts +252 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"userReportTypes.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/userReportTypes.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { ALL_USER_REPORT_REASON_CODES, REPORT_REASON_FUNNEL, USER_REPORT_DETAILS_MAX_LENGTH, USER_REPORT_EVIDENCE_MAX_BYTES, USER_REPORT_EVIDENCE_MAX_FILES, getUserReportReasonLabel, getUserReportReasonPath, isUserReportReasonCode, } from "../userReportTypes.js";
|
|
2
|
+
describe("userReportTypes", () => {
|
|
3
|
+
describe("REPORT_REASON_FUNNEL", () => {
|
|
4
|
+
it("contains the six top-level categories", () => {
|
|
5
|
+
const codes = REPORT_REASON_FUNNEL.map((c) => c.code);
|
|
6
|
+
expect(codes).toEqual([
|
|
7
|
+
"HARASSMENT",
|
|
8
|
+
"IMPERSONATION",
|
|
9
|
+
"SPAM",
|
|
10
|
+
"UNSAFE",
|
|
11
|
+
"INAPPROPRIATE",
|
|
12
|
+
"OTHER",
|
|
13
|
+
]);
|
|
14
|
+
});
|
|
15
|
+
it("each category has at least one sub-reason", () => {
|
|
16
|
+
for (const cat of REPORT_REASON_FUNNEL) {
|
|
17
|
+
expect(cat.reasons.length).toBeGreaterThan(0);
|
|
18
|
+
for (const r of cat.reasons) {
|
|
19
|
+
expect(r.code).toBeTruthy();
|
|
20
|
+
expect(r.label).toBeTruthy();
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
it("sub-reason codes are unique across the funnel", () => {
|
|
25
|
+
const seen = new Set();
|
|
26
|
+
for (const cat of REPORT_REASON_FUNNEL) {
|
|
27
|
+
for (const r of cat.reasons) {
|
|
28
|
+
expect(seen.has(r.code)).toBe(false);
|
|
29
|
+
seen.add(r.code);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
it("ALL_USER_REPORT_REASON_CODES mirrors the funnel", () => {
|
|
34
|
+
const flat = REPORT_REASON_FUNNEL.flatMap((c) => c.reasons.map((r) => r.code));
|
|
35
|
+
expect([...ALL_USER_REPORT_REASON_CODES]).toEqual(flat);
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
describe("isUserReportReasonCode", () => {
|
|
39
|
+
it("accepts any code in the funnel", () => {
|
|
40
|
+
for (const code of ALL_USER_REPORT_REASON_CODES) {
|
|
41
|
+
expect(isUserReportReasonCode(code)).toBe(true);
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
it.each([
|
|
45
|
+
["unknown_code"],
|
|
46
|
+
[""],
|
|
47
|
+
["spam_promotion"],
|
|
48
|
+
["HARASSMENT"],
|
|
49
|
+
])("rejects unknown / wrong-shape value: %s", (val) => {
|
|
50
|
+
expect(isUserReportReasonCode(val)).toBe(false);
|
|
51
|
+
});
|
|
52
|
+
it("rejects non-string values", () => {
|
|
53
|
+
expect(isUserReportReasonCode(undefined)).toBe(false);
|
|
54
|
+
expect(isUserReportReasonCode(null)).toBe(false);
|
|
55
|
+
expect(isUserReportReasonCode(42)).toBe(false);
|
|
56
|
+
expect(isUserReportReasonCode({})).toBe(false);
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
describe("getUserReportReasonLabel / getUserReportReasonPath", () => {
|
|
60
|
+
it("returns the human label for a known code", () => {
|
|
61
|
+
expect(getUserReportReasonLabel("SPAM_SCAM")).toBe("Scam, fraud, or phishing");
|
|
62
|
+
});
|
|
63
|
+
it("returns the original code when unknown", () => {
|
|
64
|
+
expect(getUserReportReasonLabel("nope")).toBe("nope");
|
|
65
|
+
});
|
|
66
|
+
it("formats path as 'Category › Sub-reason'", () => {
|
|
67
|
+
expect(getUserReportReasonPath("HARASSMENT_BULLYING")).toBe("Harassment or bullying › Bullying or repeated targeting");
|
|
68
|
+
});
|
|
69
|
+
it("returns the original code for path lookup of unknown", () => {
|
|
70
|
+
expect(getUserReportReasonPath("missing")).toBe("missing");
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
describe("limits constants", () => {
|
|
74
|
+
it("are conservative enough for a friendly UI but not absurd", () => {
|
|
75
|
+
expect(USER_REPORT_EVIDENCE_MAX_FILES).toBe(5);
|
|
76
|
+
expect(USER_REPORT_EVIDENCE_MAX_BYTES).toBe(25 * 1024 * 1024);
|
|
77
|
+
expect(USER_REPORT_DETAILS_MAX_LENGTH).toBe(4000);
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
//# sourceMappingURL=userReportTypes.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"userReportTypes.test.js","sourceRoot":"","sources":["../../src/__tests__/userReportTypes.test.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -8,6 +8,7 @@ export * from "./partnerStoreTypes.js";
|
|
|
8
8
|
export * from "./bashFeedTypes.js";
|
|
9
9
|
export * from "./membershipDefinitions.js";
|
|
10
10
|
export * from "./aiApproval.js";
|
|
11
|
+
export * from "./userReportTypes.js";
|
|
11
12
|
export { $Enums, AISpecialistFormat, AiApprovalVerdict, AVTechnicianFormat, AgeRange, AmbashadorApplicationStatus, AudioVisualSupportSubType, AuditAction, AuditCategory, AuditSeverity, BalloonArtFormat, BalloonsFormat, BartenderFormat, BashAssociationType, BashPassTier, BashAvailabilityWindow, BashEventDressTags, BashEventSource, BashEventType, BashEventVibeTags, BashFeedReportReason, BashStatus, BeverageCartFormat, BirthdayCityResourceSuggestionStatus, BirthdayCityResourceType, BirthdayDealSuggestionStatus, BirthdayFreebieCategory, BirthdayFreebieExpirationType, BirthdayFreebieSector, BirthdayFreebieSource, BirthdayOfferExpirationType, BirthdayOfferQualityTier, BirthdayOfferSourceType, BookkeepersFormat, CPAsFormat, CateringFormat, CelebrityAppearanceFormat, CenterpiecesFormat, ChairsFormat, ClaimRequestStatus, CleaningSubType, ClubAdminRole, ClubMemberStatus, ConnectionStatus, ConsentType, ConsultantFormat, CreditSourceType, CreditTransactionStatus, CrewHandFormat, CustomSignageFormat, DJLightingFormat, DanceFloorFormat, DayOfWeek, DecorAndDesignSubType, DecoratorFormat, DeepCleaningFormat, DessertBarFormat, DiscountScope, DiscountSourceType, DroneShotsFormat, Education, EmceeFormat, EntityType, EventFormat, EventManagerFormat, EventPlannerFormat, EventPlanningAndManagementSubType, EventServiceType, EventTemplateCategory, ExhibitorBookingStatus, FinancingSubType, FlyerCampaignStatus, FlyerConversionType, FlyerFulfillmentType, FlyerSponsorSlot, FlyerTargetingMode, FloralFormat, FoodAndBeverageSubType, FoodCartStandFormat, FoodTruckFormat, FurnitureStylingFormat, Gender, GeneratorsFormat, GoProWearerFormat, GraphicDesignerFormat, GreeterFormat, GroupMemberStatus, GroupVisibility, GuestRegistrationFormat, GuestShuttleFormat, InfluencerFormat, InteriorDesignerFormat, InvestmentType, InvestorFormat, LawyersFormat, LightCleaningFormat, LightingFormat, LightingTechFormat, LimoFormat, LinensFormat, LongTermLoanFormat, LocationRevealAudience, LocationRevealMode, MediaEditorFormat, MediaProductionAndCreativeSubType, MediaType, NotificationActionType, NotificationPriority, NotificationType, Occupation, OfferClaimStatus, OfferDiscountType, OrganizationType, OutreachLeadSource, OutreachLeadStage, OtherSubType, PASystemFormat, ParkingManagementFormat, ParticipantStatus, PartyBusFormat, PendingGiftStatus, PhotoBoothFormat, PhotographerFormat, PredictionSource, PredictionType, PricingModel, PricingType, Privacy, PrivateChefFormat, PrizePaymentMethod, PrizePaymentStatus, PartnerTier, PrizeType, ProjectorSetupFormat, PromoterFormat, PromotionAndMarketingSubType, RecurrenceEndsType, RecurringFrequency, RedemptionMethod, RentalEquipmentSubType, ReportStatus, ReportingFormat, RewardReportStatus, ScoutTier, SecurityFormat, ServiceAddonCatalogKind, ServiceAddonRequestStatus, ServiceAddonStatus, ServiceAnalyticsEvent, ServiceBookingFeeType, ServiceBookingRateOption, ServiceBookingStatus, ServiceBookingType, ServiceCancellationPolicy, ServiceCategoryRequestStatus, ServiceCondition, ServiceIncludeResponse, ServiceIncludeStatus, ServicePreference, ServiceRatePricingType, ServiceRateType, ServiceStatus, ServiceSubscriptionStatus, Sex, ShortTermLoanFormat, SocialMediaPlatform, SoundEngineerFormat, SpecialOfferType, SponsorBookingStatus, SponsorTier, SponsorType, StaffingSubType, StageTechFormat, StagingFormat, TableStylingFormat, TablesFormat, TaskStatus, TentsFormat, ThankYouCardsFormat, TicketStatus, TransferRequestStatus, TransportationSubType, UserIntent, UserRole, UserStatus, UserSubscriptionStatus, ValetFormat, ValetServiceFormat, VendedProductType, VendorBidStatus, VendorBookingStatus, VendorServiceType, VenuePricingPlan, VideographerFormat, VirtualAssistantFormat, VisibilityPreference, VolunteerServiceStatus, VoteType, VoucherStatus, SuggestionType, SuggestionStatus, } from "@prisma/client";
|
|
12
13
|
export type { AmountOfGuests, AssociatedBash, AssociatedService, BashEvent, BashEventPromoCode, BashFeedPost, BlockedUser, CompetitionParticipant, Contact, CustomBashEventType, DocumentID, EventTask, Exhibitor, FlyerArtwork, FlyerCampaign, FlyerCampaignSponsor, FlyerConversion, FlyerMailingRoute, GoogleReview, PostalCarrierRoute, Invitation, Link, Media, Organization, PartnerAdSlot, PartnerBrand, PartnerProduct, Recurrence, Service, ServiceAddon, ServiceBooking, ServiceRate, SocialMediaProfile, Sponsor, StripeAccount, TargetAudience, TaskInvitation, Ticket, TicketTier, User, UserPreferences, UserSubscription, Vendor, PotluckItem, BashEventSuggestion, ItemUpvote, SuggestionUpvote, } from "@prisma/client";
|
|
13
14
|
export * from "./utils/addressUtils.js";
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,kBAAkB,CAAC;AACjC,cAAc,qBAAqB,CAAC;AACpC,cAAc,qBAAqB,CAAC;AACpC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,wCAAwC,CAAC;AACvD,cAAc,sBAAsB,CAAC;AACrC,cAAc,wBAAwB,CAAC;AACvC,cAAc,oBAAoB,CAAC;AACnC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -8,6 +8,7 @@ export * from "./partnerStoreTypes.js";
|
|
|
8
8
|
export * from "./bashFeedTypes.js";
|
|
9
9
|
export * from "./membershipDefinitions.js";
|
|
10
10
|
export * from "./aiApproval.js";
|
|
11
|
+
export * from "./userReportTypes.js";
|
|
11
12
|
// Re-export ALL Prisma enums as values (usable as runtime constants, e.g. ServiceTypes.Entertainment)
|
|
12
13
|
// Excludes enums already re-exported by definitions.ts and membershipDefinitions.ts:
|
|
13
14
|
// definitions.ts: YearsOfExperience, EntertainmentServiceType, MusicGenreType, ServiceTypes,
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,kBAAkB,CAAC;AACjC,cAAc,qBAAqB,CAAC;AACpC,cAAc,qBAAqB,CAAC;AACpC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,wCAAwC,CAAC;AACvD,cAAc,sBAAsB,CAAC;AACrC,cAAc,wBAAwB,CAAC;AACvC,cAAc,oBAAoB,CAAC;AACnC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared types for the user-report flow (public profile → admin dashboard).
|
|
3
|
+
* Reporting is a real-stakes flow, so all user-facing copy here is plain and specific.
|
|
4
|
+
*
|
|
5
|
+
* The selected sub-reason code is stored verbatim in `UserReport.reason`.
|
|
6
|
+
* Free text is stored in `UserReport.details`.
|
|
7
|
+
*/
|
|
8
|
+
/** Top-level funnel category. */
|
|
9
|
+
export type UserReportCategoryCode = "HARASSMENT" | "IMPERSONATION" | "SPAM" | "UNSAFE" | "INAPPROPRIATE" | "OTHER";
|
|
10
|
+
/** Specific sub-reason. Persisted as `UserReport.reason`. */
|
|
11
|
+
export type UserReportReasonCode = "HARASSMENT_BULLYING" | "HARASSMENT_THREATS" | "HARASSMENT_HATE_SPEECH" | "HARASSMENT_TARGETED" | "IMPERSONATION_ME" | "IMPERSONATION_SOMEONE_ELSE" | "IMPERSONATION_FAKE_BUSINESS" | "SPAM_PROMOTION" | "SPAM_SCAM" | "SPAM_FAKE_ENGAGEMENT" | "UNSAFE_VIOLENT" | "UNSAFE_SELF_HARM" | "UNSAFE_MINORS" | "UNSAFE_ILLEGAL_GOODS" | "INAPPROPRIATE_NUDITY_SEXUAL" | "INAPPROPRIATE_GRAPHIC_VIOLENCE" | "INAPPROPRIATE_OFFENSIVE" | "OTHER";
|
|
12
|
+
export interface UserReportReasonOption {
|
|
13
|
+
code: UserReportReasonCode;
|
|
14
|
+
label: string;
|
|
15
|
+
}
|
|
16
|
+
export interface UserReportCategoryOption {
|
|
17
|
+
code: UserReportCategoryCode;
|
|
18
|
+
label: string;
|
|
19
|
+
description: string;
|
|
20
|
+
reasons: UserReportReasonOption[];
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Funnel definition shared between the report modal (UI) and the API
|
|
24
|
+
* (validation that the chosen `reason` is a known code).
|
|
25
|
+
*/
|
|
26
|
+
export declare const REPORT_REASON_FUNNEL: readonly UserReportCategoryOption[];
|
|
27
|
+
/** All valid reason codes (built from the funnel — no drift). */
|
|
28
|
+
export declare const ALL_USER_REPORT_REASON_CODES: readonly UserReportReasonCode[];
|
|
29
|
+
export declare const isUserReportReasonCode: (value: unknown) => value is UserReportReasonCode;
|
|
30
|
+
/** Look up the human-readable label for a reason code. */
|
|
31
|
+
export declare function getUserReportReasonLabel(code: string): string;
|
|
32
|
+
/** Look up "Category › Reason" path for a stored reason code. */
|
|
33
|
+
export declare function getUserReportReasonPath(code: string): string;
|
|
34
|
+
/** Constants used by the evidence upload route. */
|
|
35
|
+
export declare const USER_REPORT_EVIDENCE_MAX_FILES = 5;
|
|
36
|
+
export declare const USER_REPORT_EVIDENCE_MAX_BYTES: number;
|
|
37
|
+
/** Max free-text length for `UserReport.details`. */
|
|
38
|
+
export declare const USER_REPORT_DETAILS_MAX_LENGTH = 4000;
|
|
39
|
+
export interface CreateUserReportRequest {
|
|
40
|
+
reportedId: string;
|
|
41
|
+
reason: UserReportReasonCode;
|
|
42
|
+
details?: string;
|
|
43
|
+
bashEventId?: string;
|
|
44
|
+
}
|
|
45
|
+
export interface UserReportEvidenceDto {
|
|
46
|
+
id: string;
|
|
47
|
+
url: string;
|
|
48
|
+
mimetype: string;
|
|
49
|
+
sizeBytes: number;
|
|
50
|
+
createdAt: string;
|
|
51
|
+
}
|
|
52
|
+
interface UserReportUserSummary {
|
|
53
|
+
id: string;
|
|
54
|
+
email: string | null;
|
|
55
|
+
username: string | null;
|
|
56
|
+
givenName: string | null;
|
|
57
|
+
familyName: string | null;
|
|
58
|
+
image: string | null;
|
|
59
|
+
uploadedImage: string | null;
|
|
60
|
+
}
|
|
61
|
+
export interface UserReportListItem {
|
|
62
|
+
id: string;
|
|
63
|
+
reporterId: string;
|
|
64
|
+
reportedId: string;
|
|
65
|
+
reason: UserReportReasonCode | string;
|
|
66
|
+
details: string;
|
|
67
|
+
status: string;
|
|
68
|
+
createdAt: string;
|
|
69
|
+
reviewerId: string | null;
|
|
70
|
+
reviewedAt: string | null;
|
|
71
|
+
reviewNotes: string | null;
|
|
72
|
+
evidenceCount: number;
|
|
73
|
+
reporter: UserReportUserSummary;
|
|
74
|
+
reported: UserReportUserSummary;
|
|
75
|
+
}
|
|
76
|
+
export interface UserReportDetail extends Omit<UserReportListItem, "evidenceCount"> {
|
|
77
|
+
evidence: UserReportEvidenceDto[];
|
|
78
|
+
/** Other pending reports against the same reported user. */
|
|
79
|
+
otherPendingReportsAgainstReported: number;
|
|
80
|
+
/** Total active demerits already on the reported user. */
|
|
81
|
+
reportedActiveDemerits: number;
|
|
82
|
+
}
|
|
83
|
+
/** Action verbs for the moderation dashboard (PATCH /reports/:id). */
|
|
84
|
+
export type ReviewUserReportAction = "ignore" | "suspend" | "demerit" | "deactivate" | "delete";
|
|
85
|
+
interface ReviewIgnore {
|
|
86
|
+
action: "ignore";
|
|
87
|
+
notes?: string;
|
|
88
|
+
}
|
|
89
|
+
interface ReviewSuspend {
|
|
90
|
+
action: "suspend";
|
|
91
|
+
reason: string;
|
|
92
|
+
/** ISO date; if omitted the user is suspended indefinitely. */
|
|
93
|
+
until?: string;
|
|
94
|
+
notes?: string;
|
|
95
|
+
}
|
|
96
|
+
interface ReviewDemerit {
|
|
97
|
+
action: "demerit";
|
|
98
|
+
reason: string;
|
|
99
|
+
/** Defaults to 1 server-side. */
|
|
100
|
+
points?: number;
|
|
101
|
+
notes?: string;
|
|
102
|
+
}
|
|
103
|
+
interface ReviewDeactivate {
|
|
104
|
+
action: "deactivate";
|
|
105
|
+
reason: string;
|
|
106
|
+
notes?: string;
|
|
107
|
+
}
|
|
108
|
+
interface ReviewDelete {
|
|
109
|
+
action: "delete";
|
|
110
|
+
reason: string;
|
|
111
|
+
/** Reviewer must type the reported user's username/email to confirm. */
|
|
112
|
+
confirmation: string;
|
|
113
|
+
notes?: string;
|
|
114
|
+
}
|
|
115
|
+
export type ReviewUserReportRequest = ReviewIgnore | ReviewSuspend | ReviewDemerit | ReviewDeactivate | ReviewDelete;
|
|
116
|
+
export {};
|
|
117
|
+
//# sourceMappingURL=userReportTypes.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"userReportTypes.d.ts","sourceRoot":"","sources":["../src/userReportTypes.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared types for the user-report flow (public profile → admin dashboard).
|
|
3
|
+
* Reporting is a real-stakes flow, so all user-facing copy here is plain and specific.
|
|
4
|
+
*
|
|
5
|
+
* The selected sub-reason code is stored verbatim in `UserReport.reason`.
|
|
6
|
+
* Free text is stored in `UserReport.details`.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Funnel definition shared between the report modal (UI) and the API
|
|
10
|
+
* (validation that the chosen `reason` is a known code).
|
|
11
|
+
*/
|
|
12
|
+
export const REPORT_REASON_FUNNEL = [
|
|
13
|
+
{
|
|
14
|
+
code: "HARASSMENT",
|
|
15
|
+
label: "Harassment or bullying",
|
|
16
|
+
description: "Behavior that targets, intimidates, or threatens someone.",
|
|
17
|
+
reasons: [
|
|
18
|
+
{ code: "HARASSMENT_BULLYING", label: "Bullying or repeated targeting" },
|
|
19
|
+
{ code: "HARASSMENT_THREATS", label: "Threats of violence or harm" },
|
|
20
|
+
{ code: "HARASSMENT_HATE_SPEECH", label: "Hate speech or discrimination" },
|
|
21
|
+
{ code: "HARASSMENT_TARGETED", label: "Targeting me or someone I know" },
|
|
22
|
+
],
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
code: "IMPERSONATION",
|
|
26
|
+
label: "Impersonation",
|
|
27
|
+
description: "Pretending to be a person or business they aren't.",
|
|
28
|
+
reasons: [
|
|
29
|
+
{ code: "IMPERSONATION_ME", label: "Pretending to be me" },
|
|
30
|
+
{ code: "IMPERSONATION_SOMEONE_ELSE", label: "Pretending to be someone else" },
|
|
31
|
+
{ code: "IMPERSONATION_FAKE_BUSINESS", label: "Pretending to be a business or venue" },
|
|
32
|
+
],
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
code: "SPAM",
|
|
36
|
+
label: "Spam or scam",
|
|
37
|
+
description: "Unsolicited promotion, scams, or fake engagement.",
|
|
38
|
+
reasons: [
|
|
39
|
+
{ code: "SPAM_PROMOTION", label: "Unsolicited promotion" },
|
|
40
|
+
{ code: "SPAM_SCAM", label: "Scam, fraud, or phishing" },
|
|
41
|
+
{ code: "SPAM_FAKE_ENGAGEMENT", label: "Fake followers, ratings, or reviews" },
|
|
42
|
+
],
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
code: "UNSAFE",
|
|
46
|
+
label: "Unsafe or threatening behavior",
|
|
47
|
+
description: "Activity that puts people at risk.",
|
|
48
|
+
reasons: [
|
|
49
|
+
{ code: "UNSAFE_VIOLENT", label: "Violent behavior" },
|
|
50
|
+
{ code: "UNSAFE_SELF_HARM", label: "Self-harm or suicide" },
|
|
51
|
+
{ code: "UNSAFE_MINORS", label: "Endangering a minor" },
|
|
52
|
+
{ code: "UNSAFE_ILLEGAL_GOODS", label: "Illegal goods or services" },
|
|
53
|
+
],
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
code: "INAPPROPRIATE",
|
|
57
|
+
label: "Inappropriate content",
|
|
58
|
+
description: "Content that doesn't belong on Bash.",
|
|
59
|
+
reasons: [
|
|
60
|
+
{ code: "INAPPROPRIATE_NUDITY_SEXUAL", label: "Nudity or sexual content" },
|
|
61
|
+
{ code: "INAPPROPRIATE_GRAPHIC_VIOLENCE", label: "Graphic violence" },
|
|
62
|
+
{ code: "INAPPROPRIATE_OFFENSIVE", label: "Offensive or disturbing content" },
|
|
63
|
+
],
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
code: "OTHER",
|
|
67
|
+
label: "Something else",
|
|
68
|
+
description: "Tell us in your own words.",
|
|
69
|
+
reasons: [{ code: "OTHER", label: "Other (describe below)" }],
|
|
70
|
+
},
|
|
71
|
+
];
|
|
72
|
+
/** All valid reason codes (built from the funnel — no drift). */
|
|
73
|
+
export const ALL_USER_REPORT_REASON_CODES = REPORT_REASON_FUNNEL.flatMap((c) => c.reasons.map((r) => r.code));
|
|
74
|
+
export const isUserReportReasonCode = (value) => typeof value === "string" &&
|
|
75
|
+
ALL_USER_REPORT_REASON_CODES.includes(value);
|
|
76
|
+
/** Look up the human-readable label for a reason code. */
|
|
77
|
+
export function getUserReportReasonLabel(code) {
|
|
78
|
+
for (const cat of REPORT_REASON_FUNNEL) {
|
|
79
|
+
const match = cat.reasons.find((r) => r.code === code);
|
|
80
|
+
if (match)
|
|
81
|
+
return match.label;
|
|
82
|
+
}
|
|
83
|
+
return code;
|
|
84
|
+
}
|
|
85
|
+
/** Look up "Category › Reason" path for a stored reason code. */
|
|
86
|
+
export function getUserReportReasonPath(code) {
|
|
87
|
+
for (const cat of REPORT_REASON_FUNNEL) {
|
|
88
|
+
const match = cat.reasons.find((r) => r.code === code);
|
|
89
|
+
if (match)
|
|
90
|
+
return `${cat.label} › ${match.label}`;
|
|
91
|
+
}
|
|
92
|
+
return code;
|
|
93
|
+
}
|
|
94
|
+
/** Constants used by the evidence upload route. */
|
|
95
|
+
export const USER_REPORT_EVIDENCE_MAX_FILES = 5;
|
|
96
|
+
export const USER_REPORT_EVIDENCE_MAX_BYTES = 25 * 1024 * 1024;
|
|
97
|
+
/** Max free-text length for `UserReport.details`. */
|
|
98
|
+
export const USER_REPORT_DETAILS_MAX_LENGTH = 4000;
|
|
99
|
+
//# sourceMappingURL=userReportTypes.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"userReportTypes.js","sourceRoot":"","sources":["../src/userReportTypes.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
package/prisma/schema.prisma
CHANGED
|
@@ -688,6 +688,14 @@ model BashEvent {
|
|
|
688
688
|
platformFeeEnabled Boolean @default(false)
|
|
689
689
|
/// Bash platform take rate on ticket gross (e.g. 0.03 = 3%). Ignored when platformFeeEnabled is false.
|
|
690
690
|
platformFeeRate Float @default(0.03)
|
|
691
|
+
/// When true, door staff can use pay-at-door QR and terminal flows. When false, door pay CTAs and endpoints return 403.
|
|
692
|
+
allowDoorQrPayments Boolean @default(true)
|
|
693
|
+
/// Host uses a Stripe card reader (Terminal) at the door; door app can run card on reader.
|
|
694
|
+
hasStripeTerminal Boolean @default(false)
|
|
695
|
+
/// Host uses Tap to Pay on a supported device (no separate reader). Door flow when enabled.
|
|
696
|
+
hasTapToPay Boolean @default(true)
|
|
697
|
+
/// Optional Stripe Terminal Location id on the host's connected account.
|
|
698
|
+
stripeTerminalLocationId String?
|
|
691
699
|
/// Optional: attendees can tag which act they came to see; drives attribution reports and future payouts.
|
|
692
700
|
artistAttributionEnabled Boolean @default(false)
|
|
693
701
|
/// "percentage" | "flat" — how artistPayoutRate is interpreted for suggested splits (display/export).
|
|
@@ -2531,6 +2539,7 @@ model User {
|
|
|
2531
2539
|
reportsReceived UserReport[] @relation("ReportsReceived")
|
|
2532
2540
|
reportsMade UserReport[] @relation("ReportsMade")
|
|
2533
2541
|
reportsReviewed UserReport[] @relation("ReportsReviewed")
|
|
2542
|
+
reportEvidenceUploaded UserReportEvidence[] @relation("UserReportEvidenceUploader")
|
|
2534
2543
|
userStats UserStats?
|
|
2535
2544
|
userSubscription UserSubscription?
|
|
2536
2545
|
vendorBid VendorBid[]
|
|
@@ -4418,6 +4427,7 @@ model UserReport {
|
|
|
4418
4427
|
reviewedAt DateTime?
|
|
4419
4428
|
reviewNotes String?
|
|
4420
4429
|
demerits Demerit[]
|
|
4430
|
+
evidence UserReportEvidence[]
|
|
4421
4431
|
bashEvent BashEvent? @relation(fields: [bashEventId], references: [id])
|
|
4422
4432
|
reported User @relation("ReportsReceived", fields: [reportedId], references: [id], onDelete: Cascade)
|
|
4423
4433
|
reporter User @relation("ReportsMade", fields: [reporterId], references: [id], onDelete: Cascade)
|
|
@@ -4429,6 +4439,22 @@ model UserReport {
|
|
|
4429
4439
|
@@index([status])
|
|
4430
4440
|
}
|
|
4431
4441
|
|
|
4442
|
+
model UserReportEvidence {
|
|
4443
|
+
id String @id @default(cuid())
|
|
4444
|
+
reportId String
|
|
4445
|
+
url String
|
|
4446
|
+
s3Key String
|
|
4447
|
+
mimetype String
|
|
4448
|
+
sizeBytes Int
|
|
4449
|
+
uploadedById String
|
|
4450
|
+
createdAt DateTime @default(now())
|
|
4451
|
+
report UserReport @relation(fields: [reportId], references: [id], onDelete: Cascade)
|
|
4452
|
+
uploadedBy User @relation("UserReportEvidenceUploader", fields: [uploadedById], references: [id], onDelete: Cascade)
|
|
4453
|
+
|
|
4454
|
+
@@index([reportId])
|
|
4455
|
+
@@index([uploadedById])
|
|
4456
|
+
}
|
|
4457
|
+
|
|
4432
4458
|
model Demerit {
|
|
4433
4459
|
id String @id @default(cuid())
|
|
4434
4460
|
userId String
|
|
@@ -7497,6 +7523,10 @@ enum AuditAction {
|
|
|
7497
7523
|
USER_SUSPENDED
|
|
7498
7524
|
USER_UNSUSPENDED
|
|
7499
7525
|
USER_DELETED
|
|
7526
|
+
USER_DEACTIVATED
|
|
7527
|
+
USER_DEMERITED
|
|
7528
|
+
USER_REPORTED
|
|
7529
|
+
USER_REPORT_IGNORED
|
|
7500
7530
|
USER_DATA_EXPORTED
|
|
7501
7531
|
PROFILE_UPDATED
|
|
7502
7532
|
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ALL_USER_REPORT_REASON_CODES,
|
|
3
|
+
REPORT_REASON_FUNNEL,
|
|
4
|
+
USER_REPORT_DETAILS_MAX_LENGTH,
|
|
5
|
+
USER_REPORT_EVIDENCE_MAX_BYTES,
|
|
6
|
+
USER_REPORT_EVIDENCE_MAX_FILES,
|
|
7
|
+
getUserReportReasonLabel,
|
|
8
|
+
getUserReportReasonPath,
|
|
9
|
+
isUserReportReasonCode,
|
|
10
|
+
} from "../userReportTypes.js";
|
|
11
|
+
|
|
12
|
+
describe("userReportTypes", () => {
|
|
13
|
+
describe("REPORT_REASON_FUNNEL", () => {
|
|
14
|
+
it("contains the six top-level categories", () => {
|
|
15
|
+
const codes = REPORT_REASON_FUNNEL.map((c) => c.code);
|
|
16
|
+
expect(codes).toEqual([
|
|
17
|
+
"HARASSMENT",
|
|
18
|
+
"IMPERSONATION",
|
|
19
|
+
"SPAM",
|
|
20
|
+
"UNSAFE",
|
|
21
|
+
"INAPPROPRIATE",
|
|
22
|
+
"OTHER",
|
|
23
|
+
]);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it("each category has at least one sub-reason", () => {
|
|
27
|
+
for (const cat of REPORT_REASON_FUNNEL) {
|
|
28
|
+
expect(cat.reasons.length).toBeGreaterThan(0);
|
|
29
|
+
for (const r of cat.reasons) {
|
|
30
|
+
expect(r.code).toBeTruthy();
|
|
31
|
+
expect(r.label).toBeTruthy();
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it("sub-reason codes are unique across the funnel", () => {
|
|
37
|
+
const seen = new Set<string>();
|
|
38
|
+
for (const cat of REPORT_REASON_FUNNEL) {
|
|
39
|
+
for (const r of cat.reasons) {
|
|
40
|
+
expect(seen.has(r.code)).toBe(false);
|
|
41
|
+
seen.add(r.code);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it("ALL_USER_REPORT_REASON_CODES mirrors the funnel", () => {
|
|
47
|
+
const flat = REPORT_REASON_FUNNEL.flatMap((c) => c.reasons.map((r) => r.code));
|
|
48
|
+
expect([...ALL_USER_REPORT_REASON_CODES]).toEqual(flat);
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
describe("isUserReportReasonCode", () => {
|
|
53
|
+
it("accepts any code in the funnel", () => {
|
|
54
|
+
for (const code of ALL_USER_REPORT_REASON_CODES) {
|
|
55
|
+
expect(isUserReportReasonCode(code)).toBe(true);
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it.each([
|
|
60
|
+
["unknown_code"],
|
|
61
|
+
[""],
|
|
62
|
+
["spam_promotion"],
|
|
63
|
+
["HARASSMENT"],
|
|
64
|
+
])("rejects unknown / wrong-shape value: %s", (val) => {
|
|
65
|
+
expect(isUserReportReasonCode(val)).toBe(false);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it("rejects non-string values", () => {
|
|
69
|
+
expect(isUserReportReasonCode(undefined)).toBe(false);
|
|
70
|
+
expect(isUserReportReasonCode(null)).toBe(false);
|
|
71
|
+
expect(isUserReportReasonCode(42)).toBe(false);
|
|
72
|
+
expect(isUserReportReasonCode({})).toBe(false);
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
describe("getUserReportReasonLabel / getUserReportReasonPath", () => {
|
|
77
|
+
it("returns the human label for a known code", () => {
|
|
78
|
+
expect(getUserReportReasonLabel("SPAM_SCAM")).toBe("Scam, fraud, or phishing");
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it("returns the original code when unknown", () => {
|
|
82
|
+
expect(getUserReportReasonLabel("nope")).toBe("nope");
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it("formats path as 'Category › Sub-reason'", () => {
|
|
86
|
+
expect(getUserReportReasonPath("HARASSMENT_BULLYING")).toBe(
|
|
87
|
+
"Harassment or bullying › Bullying or repeated targeting"
|
|
88
|
+
);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it("returns the original code for path lookup of unknown", () => {
|
|
92
|
+
expect(getUserReportReasonPath("missing")).toBe("missing");
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
describe("limits constants", () => {
|
|
97
|
+
it("are conservative enough for a friendly UI but not absurd", () => {
|
|
98
|
+
expect(USER_REPORT_EVIDENCE_MAX_FILES).toBe(5);
|
|
99
|
+
expect(USER_REPORT_EVIDENCE_MAX_BYTES).toBe(25 * 1024 * 1024);
|
|
100
|
+
expect(USER_REPORT_DETAILS_MAX_LENGTH).toBe(4000);
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
});
|
package/src/index.ts
CHANGED
|
@@ -8,6 +8,7 @@ export * from "./partnerStoreTypes.js";
|
|
|
8
8
|
export * from "./bashFeedTypes.js";
|
|
9
9
|
export * from "./membershipDefinitions.js";
|
|
10
10
|
export * from "./aiApproval.js";
|
|
11
|
+
export * from "./userReportTypes.js";
|
|
11
12
|
|
|
12
13
|
// Re-export ALL Prisma enums as values (usable as runtime constants, e.g. ServiceTypes.Entertainment)
|
|
13
14
|
// Excludes enums already re-exported by definitions.ts and membershipDefinitions.ts:
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared types for the user-report flow (public profile → admin dashboard).
|
|
3
|
+
* Reporting is a real-stakes flow, so all user-facing copy here is plain and specific.
|
|
4
|
+
*
|
|
5
|
+
* The selected sub-reason code is stored verbatim in `UserReport.reason`.
|
|
6
|
+
* Free text is stored in `UserReport.details`.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/** Top-level funnel category. */
|
|
10
|
+
export type UserReportCategoryCode =
|
|
11
|
+
| "HARASSMENT"
|
|
12
|
+
| "IMPERSONATION"
|
|
13
|
+
| "SPAM"
|
|
14
|
+
| "UNSAFE"
|
|
15
|
+
| "INAPPROPRIATE"
|
|
16
|
+
| "OTHER";
|
|
17
|
+
|
|
18
|
+
/** Specific sub-reason. Persisted as `UserReport.reason`. */
|
|
19
|
+
export type UserReportReasonCode =
|
|
20
|
+
// HARASSMENT
|
|
21
|
+
| "HARASSMENT_BULLYING"
|
|
22
|
+
| "HARASSMENT_THREATS"
|
|
23
|
+
| "HARASSMENT_HATE_SPEECH"
|
|
24
|
+
| "HARASSMENT_TARGETED"
|
|
25
|
+
// IMPERSONATION
|
|
26
|
+
| "IMPERSONATION_ME"
|
|
27
|
+
| "IMPERSONATION_SOMEONE_ELSE"
|
|
28
|
+
| "IMPERSONATION_FAKE_BUSINESS"
|
|
29
|
+
// SPAM
|
|
30
|
+
| "SPAM_PROMOTION"
|
|
31
|
+
| "SPAM_SCAM"
|
|
32
|
+
| "SPAM_FAKE_ENGAGEMENT"
|
|
33
|
+
// UNSAFE
|
|
34
|
+
| "UNSAFE_VIOLENT"
|
|
35
|
+
| "UNSAFE_SELF_HARM"
|
|
36
|
+
| "UNSAFE_MINORS"
|
|
37
|
+
| "UNSAFE_ILLEGAL_GOODS"
|
|
38
|
+
// INAPPROPRIATE
|
|
39
|
+
| "INAPPROPRIATE_NUDITY_SEXUAL"
|
|
40
|
+
| "INAPPROPRIATE_GRAPHIC_VIOLENCE"
|
|
41
|
+
| "INAPPROPRIATE_OFFENSIVE"
|
|
42
|
+
// OTHER
|
|
43
|
+
| "OTHER";
|
|
44
|
+
|
|
45
|
+
export interface UserReportReasonOption {
|
|
46
|
+
code: UserReportReasonCode;
|
|
47
|
+
label: string;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface UserReportCategoryOption {
|
|
51
|
+
code: UserReportCategoryCode;
|
|
52
|
+
label: string;
|
|
53
|
+
description: string;
|
|
54
|
+
reasons: UserReportReasonOption[];
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Funnel definition shared between the report modal (UI) and the API
|
|
59
|
+
* (validation that the chosen `reason` is a known code).
|
|
60
|
+
*/
|
|
61
|
+
export const REPORT_REASON_FUNNEL: readonly UserReportCategoryOption[] = [
|
|
62
|
+
{
|
|
63
|
+
code: "HARASSMENT",
|
|
64
|
+
label: "Harassment or bullying",
|
|
65
|
+
description: "Behavior that targets, intimidates, or threatens someone.",
|
|
66
|
+
reasons: [
|
|
67
|
+
{ code: "HARASSMENT_BULLYING", label: "Bullying or repeated targeting" },
|
|
68
|
+
{ code: "HARASSMENT_THREATS", label: "Threats of violence or harm" },
|
|
69
|
+
{ code: "HARASSMENT_HATE_SPEECH", label: "Hate speech or discrimination" },
|
|
70
|
+
{ code: "HARASSMENT_TARGETED", label: "Targeting me or someone I know" },
|
|
71
|
+
],
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
code: "IMPERSONATION",
|
|
75
|
+
label: "Impersonation",
|
|
76
|
+
description: "Pretending to be a person or business they aren't.",
|
|
77
|
+
reasons: [
|
|
78
|
+
{ code: "IMPERSONATION_ME", label: "Pretending to be me" },
|
|
79
|
+
{ code: "IMPERSONATION_SOMEONE_ELSE", label: "Pretending to be someone else" },
|
|
80
|
+
{ code: "IMPERSONATION_FAKE_BUSINESS", label: "Pretending to be a business or venue" },
|
|
81
|
+
],
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
code: "SPAM",
|
|
85
|
+
label: "Spam or scam",
|
|
86
|
+
description: "Unsolicited promotion, scams, or fake engagement.",
|
|
87
|
+
reasons: [
|
|
88
|
+
{ code: "SPAM_PROMOTION", label: "Unsolicited promotion" },
|
|
89
|
+
{ code: "SPAM_SCAM", label: "Scam, fraud, or phishing" },
|
|
90
|
+
{ code: "SPAM_FAKE_ENGAGEMENT", label: "Fake followers, ratings, or reviews" },
|
|
91
|
+
],
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
code: "UNSAFE",
|
|
95
|
+
label: "Unsafe or threatening behavior",
|
|
96
|
+
description: "Activity that puts people at risk.",
|
|
97
|
+
reasons: [
|
|
98
|
+
{ code: "UNSAFE_VIOLENT", label: "Violent behavior" },
|
|
99
|
+
{ code: "UNSAFE_SELF_HARM", label: "Self-harm or suicide" },
|
|
100
|
+
{ code: "UNSAFE_MINORS", label: "Endangering a minor" },
|
|
101
|
+
{ code: "UNSAFE_ILLEGAL_GOODS", label: "Illegal goods or services" },
|
|
102
|
+
],
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
code: "INAPPROPRIATE",
|
|
106
|
+
label: "Inappropriate content",
|
|
107
|
+
description: "Content that doesn't belong on Bash.",
|
|
108
|
+
reasons: [
|
|
109
|
+
{ code: "INAPPROPRIATE_NUDITY_SEXUAL", label: "Nudity or sexual content" },
|
|
110
|
+
{ code: "INAPPROPRIATE_GRAPHIC_VIOLENCE", label: "Graphic violence" },
|
|
111
|
+
{ code: "INAPPROPRIATE_OFFENSIVE", label: "Offensive or disturbing content" },
|
|
112
|
+
],
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
code: "OTHER",
|
|
116
|
+
label: "Something else",
|
|
117
|
+
description: "Tell us in your own words.",
|
|
118
|
+
reasons: [{ code: "OTHER", label: "Other (describe below)" }],
|
|
119
|
+
},
|
|
120
|
+
] as const;
|
|
121
|
+
|
|
122
|
+
/** All valid reason codes (built from the funnel — no drift). */
|
|
123
|
+
export const ALL_USER_REPORT_REASON_CODES: readonly UserReportReasonCode[] =
|
|
124
|
+
REPORT_REASON_FUNNEL.flatMap((c) => c.reasons.map((r) => r.code));
|
|
125
|
+
|
|
126
|
+
export const isUserReportReasonCode = (
|
|
127
|
+
value: unknown
|
|
128
|
+
): value is UserReportReasonCode =>
|
|
129
|
+
typeof value === "string" &&
|
|
130
|
+
(ALL_USER_REPORT_REASON_CODES as readonly string[]).includes(value);
|
|
131
|
+
|
|
132
|
+
/** Look up the human-readable label for a reason code. */
|
|
133
|
+
export function getUserReportReasonLabel(code: string): string {
|
|
134
|
+
for (const cat of REPORT_REASON_FUNNEL) {
|
|
135
|
+
const match = cat.reasons.find((r) => r.code === code);
|
|
136
|
+
if (match) return match.label;
|
|
137
|
+
}
|
|
138
|
+
return code;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/** Look up "Category › Reason" path for a stored reason code. */
|
|
142
|
+
export function getUserReportReasonPath(code: string): string {
|
|
143
|
+
for (const cat of REPORT_REASON_FUNNEL) {
|
|
144
|
+
const match = cat.reasons.find((r) => r.code === code);
|
|
145
|
+
if (match) return `${cat.label} › ${match.label}`;
|
|
146
|
+
}
|
|
147
|
+
return code;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/** Constants used by the evidence upload route. */
|
|
151
|
+
export const USER_REPORT_EVIDENCE_MAX_FILES = 5;
|
|
152
|
+
export const USER_REPORT_EVIDENCE_MAX_BYTES = 25 * 1024 * 1024;
|
|
153
|
+
|
|
154
|
+
/** Max free-text length for `UserReport.details`. */
|
|
155
|
+
export const USER_REPORT_DETAILS_MAX_LENGTH = 4000;
|
|
156
|
+
|
|
157
|
+
// ─── DTOs ────────────────────────────────────────────────────────────────────
|
|
158
|
+
|
|
159
|
+
export interface CreateUserReportRequest {
|
|
160
|
+
reportedId: string;
|
|
161
|
+
reason: UserReportReasonCode;
|
|
162
|
+
details?: string;
|
|
163
|
+
bashEventId?: string;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export interface UserReportEvidenceDto {
|
|
167
|
+
id: string;
|
|
168
|
+
url: string;
|
|
169
|
+
mimetype: string;
|
|
170
|
+
sizeBytes: number;
|
|
171
|
+
createdAt: string;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
interface UserReportUserSummary {
|
|
175
|
+
id: string;
|
|
176
|
+
email: string | null;
|
|
177
|
+
username: string | null;
|
|
178
|
+
givenName: string | null;
|
|
179
|
+
familyName: string | null;
|
|
180
|
+
image: string | null;
|
|
181
|
+
uploadedImage: string | null;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export interface UserReportListItem {
|
|
185
|
+
id: string;
|
|
186
|
+
reporterId: string;
|
|
187
|
+
reportedId: string;
|
|
188
|
+
reason: UserReportReasonCode | string;
|
|
189
|
+
details: string;
|
|
190
|
+
status: string;
|
|
191
|
+
createdAt: string;
|
|
192
|
+
reviewerId: string | null;
|
|
193
|
+
reviewedAt: string | null;
|
|
194
|
+
reviewNotes: string | null;
|
|
195
|
+
evidenceCount: number;
|
|
196
|
+
reporter: UserReportUserSummary;
|
|
197
|
+
reported: UserReportUserSummary;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
export interface UserReportDetail extends Omit<UserReportListItem, "evidenceCount"> {
|
|
201
|
+
evidence: UserReportEvidenceDto[];
|
|
202
|
+
/** Other pending reports against the same reported user. */
|
|
203
|
+
otherPendingReportsAgainstReported: number;
|
|
204
|
+
/** Total active demerits already on the reported user. */
|
|
205
|
+
reportedActiveDemerits: number;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/** Action verbs for the moderation dashboard (PATCH /reports/:id). */
|
|
209
|
+
export type ReviewUserReportAction =
|
|
210
|
+
| "ignore"
|
|
211
|
+
| "suspend"
|
|
212
|
+
| "demerit"
|
|
213
|
+
| "deactivate"
|
|
214
|
+
| "delete";
|
|
215
|
+
|
|
216
|
+
interface ReviewIgnore {
|
|
217
|
+
action: "ignore";
|
|
218
|
+
notes?: string;
|
|
219
|
+
}
|
|
220
|
+
interface ReviewSuspend {
|
|
221
|
+
action: "suspend";
|
|
222
|
+
reason: string;
|
|
223
|
+
/** ISO date; if omitted the user is suspended indefinitely. */
|
|
224
|
+
until?: string;
|
|
225
|
+
notes?: string;
|
|
226
|
+
}
|
|
227
|
+
interface ReviewDemerit {
|
|
228
|
+
action: "demerit";
|
|
229
|
+
reason: string;
|
|
230
|
+
/** Defaults to 1 server-side. */
|
|
231
|
+
points?: number;
|
|
232
|
+
notes?: string;
|
|
233
|
+
}
|
|
234
|
+
interface ReviewDeactivate {
|
|
235
|
+
action: "deactivate";
|
|
236
|
+
reason: string;
|
|
237
|
+
notes?: string;
|
|
238
|
+
}
|
|
239
|
+
interface ReviewDelete {
|
|
240
|
+
action: "delete";
|
|
241
|
+
reason: string;
|
|
242
|
+
/** Reviewer must type the reported user's username/email to confirm. */
|
|
243
|
+
confirmation: string;
|
|
244
|
+
notes?: string;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
export type ReviewUserReportRequest =
|
|
248
|
+
| ReviewIgnore
|
|
249
|
+
| ReviewSuspend
|
|
250
|
+
| ReviewDemerit
|
|
251
|
+
| ReviewDeactivate
|
|
252
|
+
| ReviewDelete;
|