Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 0 additions & 70 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,70 +0,0 @@
# Dependencies
node_modules/
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# Environment variables
.env
.env.local
.env.development.local
.env.test.local
.env.production.local

# Logs
logs/
*.log

# Runtime data
pids/
*.pid
*.seed
*.pid.lock

# Testing
coverage/
.nyc_output/

# IDE
.vscode/
.idea/
*.swp
*.swo
*~

# OS
.DS_Store
Thumbs.db

# Build output
dist/
build/

# Prisma
prisma/migrations/

# Temporary files
tmp/
temp/

# Lock files (use npm ci in CI/CD)
package-lock.json

# Test files and scripts
test-*.js
setup.ps1

# Extra documentation (keep only README.md, CODE_OF_CONDUCT.md, CONTRIBUTING.md)
BACKGROUND_JOBS.md
PR_DOCUMENTATION.md
QUICK_START.md
SETUP_CHECKLIST.md
SETUP_GUIDE.md
*.backup

# Agent and skills files
.agents/
skills-lock.json
# Agent/Skills files
.agents/
skills-lock.json
188 changes: 103 additions & 85 deletions prisma/schema.prisma
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema

generator client {
provider = "prisma-client-js"
}
Expand All @@ -9,7 +6,31 @@ datasource db {
provider = "postgresql"
}

model DailySubmission {
id String @id @default(uuid())
userId String
challengeId String
date DateTime
total Int
passed Int
failed Int

user User @relation(fields: [userId], references: [id], name: "UserDailySubmissions")
challenge Challenge @relation(fields: [challengeId], references: [id], name: "ChallengeDailySubmissions")

@@index([challengeId, date])
@@map("daily_submissions")
}

model User {
id String @id @default(uuid())
email String @unique
username String @unique
password String
leetcodeUsername String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt

id String @id @default(uuid())
email String @unique
username String @unique
Expand All @@ -23,126 +44,123 @@ model User {
// Email Preferences
emailPreferences Json? @default("{\"welcomeEmail\": true, \"streakReminder\": true, \"streakBroken\": true, \"weeklySummary\": true}")


// Relations

dailySubmissions DailySubmission[] @relation("UserDailySubmissions")
ownedChallenges Challenge[] @relation("ChallengeOwner")
memberships ChallengeMember[]


ownedChallenges Challenge[] @relation("ChallengeOwner")
memberships ChallengeMember[]
challengeInvites ChallengeInvite[] @relation("InviteCreator")


@@map("users")
}

model Challenge {
id String @id @default(uuid())
name String
description String?
ownerId String

// Challenge Rules
minSubmissionsPerDay Int @default(1)
difficultyFilter String[] // ["Easy", "Medium", "Hard"]
uniqueProblemConstraint Boolean @default(true)
penaltyAmount Float @default(0)

// Challenge Timeline
startDate DateTime
endDate DateTime
status ChallengeStatus @default(PENDING)
visibility ChallengeVisibility @default(PUBLIC)

createdAt DateTime @default(now())
updatedAt DateTime @updatedAt

id String @id @default(uuid())
name String
description String?
ownerId String
startDate DateTime
endDate DateTime
status ChallengeStatus @default(PENDING)
visibility ChallengeVisibility @default(PUBLIC)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt

// Relations

owner User @relation("ChallengeOwner", fields: [ownerId], references: [id], onDelete: Cascade)
dailySubmissions DailySubmission[] @relation("ChallengeDailySubmissions")
members ChallengeMember[]

owner User @relation("ChallengeOwner", fields: [ownerId], references: [id], onDelete: Cascade)
members ChallengeMember[]
dailyResults DailyResult[]
invites ChallengeInvite[]


@@map("challenges")
}

model ChallengeMember {
id String @id @default(uuid())
challengeId String
userId String
joinedAt DateTime @default(now())
isActive Boolean @default(true)
// Computed Stats
currentStreak Int @default(0)
longestStreak Int @default(0)
totalPenalties Float @default(0)
id String @id @default(uuid())
challengeId String
userId String
joinedAt DateTime @default(now())
isActive Boolean @default(true)

// Stats
currentStreak Int @default(0)
longestStreak Int @default(0)
totalPenalties Float @default(0)

// Relations
challenge Challenge @relation(fields: [challengeId], references: [id], onDelete: Cascade)
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
dailyResults DailyResult[]
penaltyLedger PenaltyLedger[]
challenge Challenge @relation(fields: [challengeId], references: [id], onDelete: Cascade)
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
dailyResults DailyResult[]
penaltyLedger PenaltyLedger[]

@@unique([challengeId, userId])
@@map("challenge_members")
}

model DailyResult {
id String @id @default(uuid())
challengeId String
memberId String
date DateTime @db.Date

// Evaluation Results
completed Boolean @default(false)
submissionsCount Int @default(0)
problemsSolved String[] // Array of problem slugs

// Metadata
evaluatedAt DateTime?
metadata Json? // Store additional data like submission details

createdAt DateTime @default(now())

id String @id @default(uuid())
challengeId String
memberId String
date DateTime @db.Date
completed Boolean @default(false)
submissionsCount Int @default(0)
problemsSolved String[]
evaluatedAt DateTime?
metadata Json?
createdAt DateTime @default(now())

// Relations
challenge Challenge @relation(fields: [challengeId], references: [id], onDelete: Cascade)
member ChallengeMember @relation(fields: [memberId], references: [id], onDelete: Cascade)
challenge Challenge @relation(fields: [challengeId], references: [id], onDelete: Cascade)
member ChallengeMember @relation(fields: [memberId], references: [id], onDelete: Cascade)

@@unique([challengeId, memberId, date])
@@index([date])
@@index([memberId])
@@map("daily_results")
}

model PenaltyLedger {
id String @id @default(uuid())
memberId String
amount Float
reason String
date DateTime @db.Date
createdAt DateTime @default(now())

// Relations
member ChallengeMember @relation(fields: [memberId], references: [id], onDelete: Cascade)

id String @id @default(uuid())
memberId String
amount Float
reason String
date DateTime @db.Date
createdAt DateTime @default(now())

member ChallengeMember @relation(fields: [memberId], references: [id], onDelete: Cascade)

@@index([memberId])
@@index([date])
@@map("penalty_ledger")
}

model ProblemMetadata {
id String @id @default(uuid())
titleSlug String @unique
questionId String
title String
difficulty String // Easy, Medium, Hard
acRate Float?
likes Int?
dislikes Int?
isPaidOnly Boolean @default(false)
topicTags String[] // Array of topic names

// Cache metadata
lastFetchedAt DateTime @default(now())
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt

id String @id @default(uuid())
titleSlug String @unique
questionId String
title String
difficulty String
acRate Float?
likes Int?
dislikes Int?
isPaidOnly Boolean @default(false)
topicTags String[]
lastFetchedAt DateTime @default(now())
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt

@@index([titleSlug])
@@index([difficulty])
@@map("problem_metadata")
Expand Down Expand Up @@ -187,4 +205,4 @@ enum ChallengeStatus {
enum ChallengeVisibility {
PUBLIC
PRIVATE
}
}
6 changes: 5 additions & 1 deletion src/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,12 @@ const responseTime = require("response-time");
const { config } = require("./config/env");
const { errorHandler, notFound } = require("./middlewares/error.middleware");
const logger = require("./utils/logger");

const adminRoutes = require("./routes/admin.routes");

const requestLogger = require("./middlewares/requestLogger");


// Import routes
const authRoutes = require("./routes/auth.routes");
const challengeRoutes = require("./routes/challenge.routes");
Expand All @@ -26,7 +30,7 @@ const createApp = () => {

// Apply strict limiting specifically to auth routes
app.use('/api/auth/', authLimiter);

app.use("/api/admin", adminRoutes);
// 2. CORS configuration
app.use(
cors({
Expand Down
14 changes: 14 additions & 0 deletions src/controllers/admin.controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
const { asyncHandler } = require("../middlewares/error.middleware");
const { getSubmissionAnalytics } = require("../services/admin.service");

const analyticsDashboard = asyncHandler(async (req, res) => {
const analytics = await getSubmissionAnalytics();

res.status(200).json({
success: true,
message: "Admin submission analytics",
data: analytics,
});
});

module.exports = { analyticsDashboard };
Loading