Implement Prototype 1 improvements: unified members, project filters, audit expansion, filtering rounds, special awards
- Unified Member Management: merge /admin/users and /admin/mentors into /admin/members with role tabs, search, pagination - Project List Filters: add search, multi-status filter, round/category/country selects, boolean toggles, URL persistence - Audit Log Expansion: track logins, round state changes, evaluation submissions, file access, role changes via shared logAudit utility - Founding Date Field: add foundedAt to Project model with CSV import support - Filtering Round System: configurable rules (field-based, document check, AI screening), execution engine, results review with override/reinstate - Special Awards System: named awards with eligibility criteria, dedicated jury, PICK_WINNER/RANKED/SCORED voting modes, AI eligibility - Dashboard resilience: wrap heavy queries in try-catch to prevent error boundary on transient DB failures - Reusable pagination component extracted to src/components/shared/pagination.tsx - Old /admin/users and /admin/mentors routes redirect to /admin/members - Prisma migration for all schema additions (additive, no data loss) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,221 @@
|
||||
-- CreateEnum
|
||||
CREATE TYPE "FilteringOutcome" AS ENUM ('PASSED', 'FILTERED_OUT', 'FLAGGED');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "FilteringRuleType" AS ENUM ('FIELD_BASED', 'DOCUMENT_CHECK', 'AI_SCREENING');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "AwardScoringMode" AS ENUM ('PICK_WINNER', 'RANKED', 'SCORED');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "AwardStatus" AS ENUM ('DRAFT', 'NOMINATIONS_OPEN', 'VOTING_OPEN', 'CLOSED', 'ARCHIVED');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "EligibilityMethod" AS ENUM ('AUTO', 'MANUAL');
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "Project" ADD COLUMN "foundedAt" TIMESTAMP(3);
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "User" ADD COLUMN "inviteToken" TEXT,
|
||||
ADD COLUMN "inviteTokenExpiresAt" TIMESTAMP(3);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "FilteringRule" (
|
||||
"id" TEXT NOT NULL,
|
||||
"roundId" TEXT NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"ruleType" "FilteringRuleType" NOT NULL,
|
||||
"configJson" JSONB NOT NULL,
|
||||
"priority" INTEGER NOT NULL DEFAULT 0,
|
||||
"isActive" BOOLEAN NOT NULL DEFAULT true,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "FilteringRule_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "FilteringResult" (
|
||||
"id" TEXT NOT NULL,
|
||||
"roundId" TEXT NOT NULL,
|
||||
"projectId" TEXT NOT NULL,
|
||||
"outcome" "FilteringOutcome" NOT NULL,
|
||||
"ruleResultsJson" JSONB,
|
||||
"aiScreeningJson" JSONB,
|
||||
"overriddenBy" TEXT,
|
||||
"overriddenAt" TIMESTAMP(3),
|
||||
"overrideReason" TEXT,
|
||||
"finalOutcome" "FilteringOutcome",
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "FilteringResult_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "SpecialAward" (
|
||||
"id" TEXT NOT NULL,
|
||||
"programId" TEXT NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"description" TEXT,
|
||||
"status" "AwardStatus" NOT NULL DEFAULT 'DRAFT',
|
||||
"criteriaText" TEXT,
|
||||
"autoTagRulesJson" JSONB,
|
||||
"scoringMode" "AwardScoringMode" NOT NULL DEFAULT 'PICK_WINNER',
|
||||
"maxRankedPicks" INTEGER,
|
||||
"votingStartAt" TIMESTAMP(3),
|
||||
"votingEndAt" TIMESTAMP(3),
|
||||
"evaluationFormId" TEXT,
|
||||
"winnerProjectId" TEXT,
|
||||
"winnerOverridden" BOOLEAN NOT NULL DEFAULT false,
|
||||
"winnerOverriddenBy" TEXT,
|
||||
"sortOrder" INTEGER NOT NULL DEFAULT 0,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "SpecialAward_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "AwardEligibility" (
|
||||
"id" TEXT NOT NULL,
|
||||
"awardId" TEXT NOT NULL,
|
||||
"projectId" TEXT NOT NULL,
|
||||
"method" "EligibilityMethod" NOT NULL DEFAULT 'AUTO',
|
||||
"eligible" BOOLEAN NOT NULL DEFAULT false,
|
||||
"aiReasoningJson" JSONB,
|
||||
"overriddenBy" TEXT,
|
||||
"overriddenAt" TIMESTAMP(3),
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "AwardEligibility_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "AwardJuror" (
|
||||
"id" TEXT NOT NULL,
|
||||
"awardId" TEXT NOT NULL,
|
||||
"userId" TEXT NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "AwardJuror_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "AwardVote" (
|
||||
"id" TEXT NOT NULL,
|
||||
"awardId" TEXT NOT NULL,
|
||||
"userId" TEXT NOT NULL,
|
||||
"projectId" TEXT NOT NULL,
|
||||
"rank" INTEGER,
|
||||
"votedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "AwardVote_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "FilteringRule_roundId_idx" ON "FilteringRule"("roundId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "FilteringRule_priority_idx" ON "FilteringRule"("priority");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "FilteringResult_roundId_idx" ON "FilteringResult"("roundId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "FilteringResult_projectId_idx" ON "FilteringResult"("projectId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "FilteringResult_outcome_idx" ON "FilteringResult"("outcome");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "FilteringResult_roundId_projectId_key" ON "FilteringResult"("roundId", "projectId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "SpecialAward_programId_idx" ON "SpecialAward"("programId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "SpecialAward_status_idx" ON "SpecialAward"("status");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "SpecialAward_sortOrder_idx" ON "SpecialAward"("sortOrder");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "AwardEligibility_awardId_idx" ON "AwardEligibility"("awardId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "AwardEligibility_projectId_idx" ON "AwardEligibility"("projectId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "AwardEligibility_eligible_idx" ON "AwardEligibility"("eligible");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "AwardEligibility_awardId_projectId_key" ON "AwardEligibility"("awardId", "projectId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "AwardJuror_awardId_idx" ON "AwardJuror"("awardId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "AwardJuror_userId_idx" ON "AwardJuror"("userId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "AwardJuror_awardId_userId_key" ON "AwardJuror"("awardId", "userId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "AwardVote_awardId_idx" ON "AwardVote"("awardId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "AwardVote_userId_idx" ON "AwardVote"("userId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "AwardVote_projectId_idx" ON "AwardVote"("projectId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "AwardVote_awardId_userId_projectId_key" ON "AwardVote"("awardId", "userId", "projectId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "User_inviteToken_key" ON "User"("inviteToken");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "FilteringRule" ADD CONSTRAINT "FilteringRule_roundId_fkey" FOREIGN KEY ("roundId") REFERENCES "Round"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "FilteringResult" ADD CONSTRAINT "FilteringResult_roundId_fkey" FOREIGN KEY ("roundId") REFERENCES "Round"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "FilteringResult" ADD CONSTRAINT "FilteringResult_projectId_fkey" FOREIGN KEY ("projectId") REFERENCES "Project"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "FilteringResult" ADD CONSTRAINT "FilteringResult_overriddenBy_fkey" FOREIGN KEY ("overriddenBy") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "SpecialAward" ADD CONSTRAINT "SpecialAward_programId_fkey" FOREIGN KEY ("programId") REFERENCES "Program"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "SpecialAward" ADD CONSTRAINT "SpecialAward_winnerProjectId_fkey" FOREIGN KEY ("winnerProjectId") REFERENCES "Project"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "AwardEligibility" ADD CONSTRAINT "AwardEligibility_awardId_fkey" FOREIGN KEY ("awardId") REFERENCES "SpecialAward"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "AwardEligibility" ADD CONSTRAINT "AwardEligibility_projectId_fkey" FOREIGN KEY ("projectId") REFERENCES "Project"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "AwardEligibility" ADD CONSTRAINT "AwardEligibility_overriddenBy_fkey" FOREIGN KEY ("overriddenBy") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "AwardJuror" ADD CONSTRAINT "AwardJuror_awardId_fkey" FOREIGN KEY ("awardId") REFERENCES "SpecialAward"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "AwardJuror" ADD CONSTRAINT "AwardJuror_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "AwardVote" ADD CONSTRAINT "AwardVote_awardId_fkey" FOREIGN KEY ("awardId") REFERENCES "SpecialAward"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "AwardVote" ADD CONSTRAINT "AwardVote_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "AwardVote" ADD CONSTRAINT "AwardVote_projectId_fkey" FOREIGN KEY ("projectId") REFERENCES "Project"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
3
prisma/migrations/migration_lock.toml
Normal file
3
prisma/migrations/migration_lock.toml
Normal file
@@ -0,0 +1,3 @@
|
||||
# Please do not edit this file manually
|
||||
# It should be added in your version-control system (i.e. Git)
|
||||
provider = "postgresql"
|
||||
@@ -259,6 +259,16 @@ model User {
|
||||
teamMemberships TeamMember[]
|
||||
mentorAssignments MentorAssignment[] @relation("MentorAssignments")
|
||||
|
||||
// Awards
|
||||
awardJurorships AwardJuror[]
|
||||
awardVotes AwardVote[]
|
||||
|
||||
// Filtering overrides
|
||||
filteringOverrides FilteringResult[] @relation("FilteringOverriddenBy")
|
||||
|
||||
// Award overrides
|
||||
awardEligibilityOverrides AwardEligibility[] @relation("AwardEligibilityOverriddenBy")
|
||||
|
||||
// NextAuth relations
|
||||
accounts Account[]
|
||||
sessions Session[]
|
||||
@@ -328,6 +338,7 @@ model Program {
|
||||
learningResources LearningResource[]
|
||||
partners Partner[]
|
||||
applicationForms ApplicationForm[]
|
||||
specialAwards SpecialAward[]
|
||||
|
||||
@@unique([name, year])
|
||||
@@index([status])
|
||||
@@ -369,6 +380,8 @@ model Round {
|
||||
evaluationForms EvaluationForm[]
|
||||
gracePeriods GracePeriod[]
|
||||
liveVotingSession LiveVotingSession?
|
||||
filteringRules FilteringRule[]
|
||||
filteringResults FilteringResult[]
|
||||
|
||||
@@index([programId])
|
||||
@@index([status])
|
||||
@@ -428,6 +441,9 @@ model Project {
|
||||
// Mentorship
|
||||
wantsMentorship Boolean @default(false)
|
||||
|
||||
// Founding date
|
||||
foundedAt DateTime? // When the project/company was founded
|
||||
|
||||
// Submission links (external, from CSV)
|
||||
phase1SubmissionUrl String?
|
||||
phase2SubmissionUrl String?
|
||||
@@ -464,6 +480,10 @@ model Project {
|
||||
submittedBy User? @relation("ProjectSubmittedBy", fields: [submittedByUserId], references: [id], onDelete: SetNull)
|
||||
teamMembers TeamMember[]
|
||||
mentorAssignment MentorAssignment?
|
||||
filteringResults FilteringResult[]
|
||||
awardEligibilities AwardEligibility[]
|
||||
awardVotes AwardVote[]
|
||||
wonAwards SpecialAward[] @relation("AwardWinner")
|
||||
|
||||
@@index([roundId])
|
||||
@@index([status])
|
||||
@@ -975,3 +995,194 @@ model MentorAssignment {
|
||||
@@index([mentorId])
|
||||
@@index([method])
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// FILTERING ROUND SYSTEM
|
||||
// =============================================================================
|
||||
|
||||
enum FilteringOutcome {
|
||||
PASSED
|
||||
FILTERED_OUT
|
||||
FLAGGED
|
||||
}
|
||||
|
||||
enum FilteringRuleType {
|
||||
FIELD_BASED
|
||||
DOCUMENT_CHECK
|
||||
AI_SCREENING
|
||||
}
|
||||
|
||||
model FilteringRule {
|
||||
id String @id @default(cuid())
|
||||
roundId String
|
||||
name String
|
||||
ruleType FilteringRuleType
|
||||
configJson Json @db.JsonB // Conditions, logic, action per rule type
|
||||
priority Int @default(0)
|
||||
isActive Boolean @default(true)
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
// Relations
|
||||
round Round @relation(fields: [roundId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@index([roundId])
|
||||
@@index([priority])
|
||||
}
|
||||
|
||||
model FilteringResult {
|
||||
id String @id @default(cuid())
|
||||
roundId String
|
||||
projectId String
|
||||
outcome FilteringOutcome
|
||||
ruleResultsJson Json? @db.JsonB // Per-rule results
|
||||
aiScreeningJson Json? @db.JsonB // AI screening details
|
||||
|
||||
// Admin override
|
||||
overriddenBy String?
|
||||
overriddenAt DateTime?
|
||||
overrideReason String? @db.Text
|
||||
finalOutcome FilteringOutcome?
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
// Relations
|
||||
round Round @relation(fields: [roundId], references: [id], onDelete: Cascade)
|
||||
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
||||
overriddenByUser User? @relation("FilteringOverriddenBy", fields: [overriddenBy], references: [id], onDelete: SetNull)
|
||||
|
||||
@@unique([roundId, projectId])
|
||||
@@index([roundId])
|
||||
@@index([projectId])
|
||||
@@index([outcome])
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// SPECIAL AWARDS SYSTEM
|
||||
// =============================================================================
|
||||
|
||||
enum AwardScoringMode {
|
||||
PICK_WINNER
|
||||
RANKED
|
||||
SCORED
|
||||
}
|
||||
|
||||
enum AwardStatus {
|
||||
DRAFT
|
||||
NOMINATIONS_OPEN
|
||||
VOTING_OPEN
|
||||
CLOSED
|
||||
ARCHIVED
|
||||
}
|
||||
|
||||
enum EligibilityMethod {
|
||||
AUTO
|
||||
MANUAL
|
||||
}
|
||||
|
||||
model SpecialAward {
|
||||
id String @id @default(cuid())
|
||||
programId String
|
||||
name String
|
||||
description String? @db.Text
|
||||
status AwardStatus @default(DRAFT)
|
||||
|
||||
// Criteria
|
||||
criteriaText String? @db.Text // Plain-language criteria for AI
|
||||
autoTagRulesJson Json? @db.JsonB // Deterministic eligibility rules
|
||||
|
||||
// Scoring
|
||||
scoringMode AwardScoringMode @default(PICK_WINNER)
|
||||
maxRankedPicks Int? // For RANKED mode
|
||||
|
||||
// Voting window
|
||||
votingStartAt DateTime?
|
||||
votingEndAt DateTime?
|
||||
|
||||
// Evaluation form (for SCORED mode)
|
||||
evaluationFormId String?
|
||||
|
||||
// Winner
|
||||
winnerProjectId String?
|
||||
winnerOverridden Boolean @default(false)
|
||||
winnerOverriddenBy String?
|
||||
|
||||
sortOrder Int @default(0)
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
// Relations
|
||||
program Program @relation(fields: [programId], references: [id], onDelete: Cascade)
|
||||
winnerProject Project? @relation("AwardWinner", fields: [winnerProjectId], references: [id], onDelete: SetNull)
|
||||
eligibilities AwardEligibility[]
|
||||
jurors AwardJuror[]
|
||||
votes AwardVote[]
|
||||
|
||||
@@index([programId])
|
||||
@@index([status])
|
||||
@@index([sortOrder])
|
||||
}
|
||||
|
||||
model AwardEligibility {
|
||||
id String @id @default(cuid())
|
||||
awardId String
|
||||
projectId String
|
||||
method EligibilityMethod @default(AUTO)
|
||||
eligible Boolean @default(false)
|
||||
aiReasoningJson Json? @db.JsonB
|
||||
|
||||
// Admin override
|
||||
overriddenBy String?
|
||||
overriddenAt DateTime?
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
// Relations
|
||||
award SpecialAward @relation(fields: [awardId], references: [id], onDelete: Cascade)
|
||||
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
||||
overriddenByUser User? @relation("AwardEligibilityOverriddenBy", fields: [overriddenBy], references: [id], onDelete: SetNull)
|
||||
|
||||
@@unique([awardId, projectId])
|
||||
@@index([awardId])
|
||||
@@index([projectId])
|
||||
@@index([eligible])
|
||||
}
|
||||
|
||||
model AwardJuror {
|
||||
id String @id @default(cuid())
|
||||
awardId String
|
||||
userId String
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
// Relations
|
||||
award SpecialAward @relation(fields: [awardId], references: [id], onDelete: Cascade)
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@unique([awardId, userId])
|
||||
@@index([awardId])
|
||||
@@index([userId])
|
||||
}
|
||||
|
||||
model AwardVote {
|
||||
id String @id @default(cuid())
|
||||
awardId String
|
||||
userId String
|
||||
projectId String
|
||||
rank Int? // For RANKED mode
|
||||
votedAt DateTime @default(now())
|
||||
|
||||
// Relations
|
||||
award SpecialAward @relation(fields: [awardId], references: [id], onDelete: Cascade)
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@unique([awardId, userId, projectId])
|
||||
@@index([awardId])
|
||||
@@index([userId])
|
||||
@@index([projectId])
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user