Repository navigation
Develop - #45
Develop#45
Conversation
…h database IDs for seed scripts
…ing and enhance scene structure
…g and improve data structure
…uncements module implementation
…t with environment variable credentials
There was a problem hiding this comment.
Sorry @Mayank9056-MM, you have reached your weekly rate limit of 500000 diff characters.
Please try again later or upgrade to continue using Sourcery
📝 WalkthroughWalkthroughThis change adds hint database tables and replaces the seed system with ordered, environment-aware scripts for chapters, scenes, challenges, users, administrators, events, and guarded database resets. ChangesDatabase Seeding and Reset
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant SeedRunner
participant Seeders
participant PrismaDatabase
SeedRunner->>Seeders: invoke seed steps sequentially
Seeders->>PrismaDatabase: upsert event, chapters, scenes, challenges, users, and admin
PrismaDatabase-->>Seeders: return seed results
Seeders-->>SeedRunner: report completion or failure
Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 inconclusive)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Reviewer's GuideRefactors the seeding system into clearly scoped scripts with chapter-number → id mapping, environment-aware execution, and simplified story/challenge content, plus introduces initial database structures for future hint support. Sequence diagram for reset-db and seed orchestrationsequenceDiagram
actor Dev
participant reset_db_ts as reset-db.ts
participant PrismaCLI as prisma_migrate_generate
participant seed_ts as seed.ts
participant prisma as PrismaClient
Dev->>reset_db_ts: run resetDatabase()
reset_db_ts->>reset_db_ts: confirm()
reset_db_ts->>PrismaCLI: prisma migrate reset
PrismaCLI-->>reset_db_ts: migrations reset
reset_db_ts->>PrismaCLI: prisma generate
PrismaCLI-->>reset_db_ts: client generated
reset_db_ts->>seed_ts: main()
seed_ts->>seed_ts: run seedEvent()
seed_ts->>seed_ts: run seedChapters()
seed_ts->>seed_ts: run seedStory()
seed_ts->>seed_ts: run seedChallenges()
seed_ts->>seed_ts: run seedUsers()
seed_ts->>seed_ts: run seedAdmin()
seed_ts->>prisma: prisma.$disconnect()
reset_db_ts->>prisma: prisma.$disconnect()
Entity relationship diagram for hints and player_hintserDiagram
users {
TEXT id PK
}
challenges {
TEXT id PK
}
hints {
TEXT id PK
TEXT challengeId FK
VARCHAR title
TEXT content
HintLevel level
INT xpCost
ContentStatus status
}
player_hints {
TEXT userId FK
TEXT hintId FK
INT xpSpent
}
challenges ||--o{ hints : has
users ||--o{ player_hints : unlocks
hints ||--o{ player_hints : is_unlocked_by
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
There was a problem hiding this comment.
Actionable comments posted: 9
🧹 Nitpick comments (3)
prisma/migrations/20260804192440_add_hint/migration.sql (1)
29-33: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick winRemove the redundant
(challengeId, level)index.
hints_challengeId_level_idxandhints_challengeId_level_keycover the same non-null columns. The unique index already supports this lookup. Keeping both indexes increases storage and write maintenance forhints.Proposed change
-CREATE INDEX "hints_challengeId_level_idx" ON "hints"("challengeId", "level"); CREATE UNIQUE INDEX "hints_challengeId_level_key" ON "hints"("challengeId", "level");🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@prisma/migrations/20260804192440_add_hint/migration.sql` around lines 29 - 33, Remove the redundant non-unique index declaration "hints_challengeId_level_idx" from the migration, while preserving the unique index "hints_challengeId_level_key" on ("challengeId", "level").scripts/seed-story.ts (1)
5-8: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueCorrect the ownership comment.
The header states this script owns Scene, Character, DialogueLine, Choice, Evidence, and UnlockRule. The script writes
Scenerows only. Narrow the comment to the models this script actually seeds.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@scripts/seed-story.ts` around lines 5 - 8, Update the ownership header comment in seed-story.ts to state that this script exclusively seeds Scene rows, removing Character, DialogueLine, Choice, Evidence, and UnlockRule from the listed ownership. Preserve the existing clarification that Chapters and Challenges belong to their respective seed scripts.scripts/seed.ts (1)
33-40: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueTwo no-op seeders have no caller.
STEPSinscripts/seed.tsomitsseedHintsandseedAnnouncements, andpackage.jsondefines no matchingseed:hintsorseed:announcementsscript. Both exports are therefore unreachable, and their skip messages never appear in a seed run. The shared root cause is that the orchestrator and the script list were not extended when these placeholders were added.
scripts/seed.ts#L33-L40: either add{ name: "hints", run: seedHints }and{ name: "announcements", run: seedAnnouncements }toSTEPS, or drop both placeholder files until the modules land.scripts/seed-hints.ts#L14-L16: keepseedHintsonly ifscripts/seed.tsruns it; otherwise delete the file.scripts/seed-announcements.ts#L6-L8: keepseedAnnouncementsonly ifscripts/seed.tsruns it; otherwise delete the file.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@scripts/seed.ts` around lines 33 - 40, Make both placeholder seeders reachable by extending the STEPS orchestrator in scripts/seed.ts with seedHints and seedAnnouncements. Retain seedHints in scripts/seed-hints.ts and seedAnnouncements in scripts/seed-announcements.ts only because they are now invoked; no direct changes are required in those files.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@package.json`:
- Around line 10-17: Update the seed script commands (seed, seed:event,
seed:chapters, seed:story, seed:challenges, seed:users, and seed:admin) to use
tsx --env-file-if-exists=.env instead of the strict --env-file=.env flag,
preserving their existing script paths and behavior.
In `@prisma/migrations/20260804192440_add_hint/migration.sql`:
- Line 11: Update the migration schema to enforce non-negative values for both
hints.xpCost and player_hints.xpSpent by adding database CHECK constraints
requiring each field to be greater than or equal to zero, while preserving their
existing INTEGER NOT NULL DEFAULT 0 definitions.
- Around line 38-45: Update the PlayerHint foreign-key cascade policies in the
migration, specifically player_hints_userId_fkey and player_hints_hintId_fkey,
so deleting a User or Hint does not erase unlock history; use the project’s
intentional preservation strategy, such as RESTRICT, soft deletion, or
archiving, while leaving the hints_challengeId_fkey relationship unchanged.
In `@scripts/reset-db.ts`:
- Around line 72-83: Update resetDatabase to explicitly disconnect Prisma after
runSeed() completes, reusing the existing Prisma client/import used by the seed
flow. Ensure disconnection occurs after successful seeding so the reset command
can exit cleanly, while preserving the existing failure handling.
- Around line 25-67: Add a target-environment validation in resetDatabase before
confirm() or prisma migrate reset runs, rejecting non-development DATABASE_URL
targets even when CI or --yes bypasses confirmation. Reuse the existing
environment/configuration symbols to identify the development database, set a
failure exit code, and return without invoking the destructive reset.
In `@scripts/seed-admin.ts`:
- Around line 38-57: Update the existing-user branch of the upsert in the
seed-admin flow so the account is always restored to the bootstrap administrator
state: set role to Role.SUPER_ADMIN and status to UserStatus.ACTIVE alongside
passwordHash. Keep the create branch and existing email-based lookup unchanged.
In `@scripts/seed-challenges.ts`:
- Around line 62-79: Update the update payload in the challenge.upsert call to
include challenge.slug alongside the other mutable challenge fields, so existing
rows keyed by chapterId and displayOrder synchronize slug changes while
preserving the current create behavior.
In `@scripts/seed-chapters.ts`:
- Around line 36-49: Update the documentation for the chapter seeding loop to
state that the upsert is keyed by chapter order, matching the `where: { order:
chapter.number }` in the `CHAPTERS` iteration; do not change the upsert
implementation.
In `@scripts/seed-hints.ts`:
- Around line 1-16: Update seedHints so its documentation matches the repository
state: remove the claim that no Hint model or migration exists and the
unsupported seed:hints script reference, then state the actual reason the
function remains a no-op. If no valid reason exists, implement seeding using the
newly added hint-related Prisma models instead.
---
Nitpick comments:
In `@prisma/migrations/20260804192440_add_hint/migration.sql`:
- Around line 29-33: Remove the redundant non-unique index declaration
"hints_challengeId_level_idx" from the migration, while preserving the unique
index "hints_challengeId_level_key" on ("challengeId", "level").
In `@scripts/seed-story.ts`:
- Around line 5-8: Update the ownership header comment in seed-story.ts to state
that this script exclusively seeds Scene rows, removing Character, DialogueLine,
Choice, Evidence, and UnlockRule from the listed ownership. Preserve the
existing clarification that Chapters and Challenges belong to their respective
seed scripts.
In `@scripts/seed.ts`:
- Around line 33-40: Make both placeholder seeders reachable by extending the
STEPS orchestrator in scripts/seed.ts with seedHints and seedAnnouncements.
Retain seedHints in scripts/seed-hints.ts and seedAnnouncements in
scripts/seed-announcements.ts only because they are now invoked; no direct
changes are required in those files.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 19fc92f9-74b4-42b8-bca6-929869712d25
📒 Files selected for processing (13)
package.jsonprisma/migrations/20260804192440_add_hint/migration.sqlscripts/reset-db.tsscripts/seed-admin.tsscripts/seed-announcements.tsscripts/seed-challenges.tsscripts/seed-chapters.tsscripts/seed-event.tsscripts/seed-hints.tsscripts/seed-story.tsscripts/seed-users.tsscripts/seed.tsscripts/utils/chapter-map.ts
| "db:reset": "tsx --env-file=.env scripts/reset-db.ts", | ||
| "seed": "tsx --env-file=.env scripts/seed.ts", | ||
| "seed:event": "tsx --env-file=.env scripts/seed-event.ts", | ||
| "seed:chapters": "tsx --env-file=.env scripts/seed-chapters.ts", | ||
| "seed:story": "tsx --env-file=.env scripts/seed-story.ts", | ||
| "seed:challenges": "tsx --env-file=.env scripts/seed-challenges.ts", | ||
| "seed:users": "tsx --env-file=.env scripts/seed-users.ts", | ||
| "seed:admin": "tsx --env-file=.env scripts/seed-admin.ts" |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🌐 Web query:
Node.js tsx --env-file missing file ENOENT --env-file-if-exists
💡 Result:
When running Node.js (or tools like tsx that pass flags through to Node.js), the --env-file flag requires the specified file to exist; if it is missing, Node.js throws an ENOENT error [1][2][3]. To prevent this error when a file might be missing (e.g., in CI environments or optional configurations), you should use the --env-file-if-exists flag instead [1][4][3]. Key points regarding these flags: - --env-file=file: Loads environment variables from the specified file. It will throw an error and exit if the file does not exist [1][3]. - --env-file-if-exists=file: Provides the same functionality as --env-file but silently ignores the file if it is missing, without throwing an error [1][3]. Since tsx acts as a wrapper that passes unrecognized flags directly to the underlying Node.js process, it fully supports these Node.js built-in flags [5][6][7]. If you are experiencing an ENOENT error with tsx, simply replace --env-file with --env-file-if-exists in your command [1][3]. Additionally, ensure that you place these flags before your script path, as Node.js and tsx interpret them as command-line options [8][7][3]. Placing them after the script name may cause them to be incorrectly parsed as arguments to your script rather than as Node.js options [8][7].
Citations:
- 1: https://nodejs.org/learn/command-line/how-to-read-environment-variables-from-nodejs
- 2: src: return error --env-file if file is missing nodejs/node#50588
- 3: https://nodejs.org/api/cli.html
- 4: Add
--env-file-if-existsnodejs/node#50993 - 5: TSX is not formatting template string variables on .env file privatenumber/tsx#681
- 6: https://github.com/esbuild-kit/tsx/blob/master/docs/index.md
- 7: fix: clarify Node flag claim in README privatenumber/tsx#418
- 8: Script argument
--env-fileis wrongly used as node option nodejs/node#54232
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '--- package.json relevant scripts ---\n'
python3 - <<'PY'
import json
from pathlib import Path
pkg = json.loads(Path('package.json').read_text())
for name in [
'db:reset','seed','seed:event','seed:chapters','seed:story',
'seed:challenges','seed:users','seed:admin'
]:
print(f'{name}: {pkg.get("scripts",{}).get(name)}')
PY
printf '\n--- seed-related imports ---\n'
rg -n '"dotenv/config"|from ["'+"'"]dotenv/config" scripts package.json || true
printf '\n--- installed tsx/node availability ---\n'
(command -v node && node --version) || true
(command -v tsx && tsx --version) || true
printf '\n--- local flag behavior probe (using installed tsx if present) ---\n'
if command -v tsx >/dev/null 2>&1; then
tmpdir="$(mktemp -d)"
cat > "$tmpdir/probe.ts" <<'TS'
console.log('node-options-value:', process.env.SECRET_VAR)
TS
export SECRET_VAR=fromEnv
printf 'tsx --env-file=.env --env-file-if-exists=.env: '
tsx --env-file=.env --env-file-if-exists=.env "$tmpdir/probe.ts" 2>&1 || true
rm -rf "$tmpdir"
fiRepository: Mayank9056-MM/ctf-silk-project
Length of output: 259
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '%s\n' '--- package.json relevant scripts ---'
python3 - <<'PY'
import json
from pathlib import Path
pkg = json.loads(Path("package.json").read_text())
for name in [
"db:reset","seed","seed:event","seed:chapters","seed:story",
"seed:challenges","seed:users","seed:admin"
]:
print(f"{name}: {pkg.get('scripts',{}).get(name)}")
PY
printf '%s\n' ''
printf '%s\n' '--- seed-related imports ---'
rg -n '"dotenv/config"|from ["'"'']dotenv/config' scripts package.json || printf '%s\n' 'no matches'
printf '%s\n' ''
printf '%s\n' '--- installed tsx/node availability ---'
(command -v node && node --version) || printf '%s\n' 'node unavailable'
(command -v tsx && tsx --version) || printf '%s\n' 'tsx unavailable'
printf '%s\n' ''
printf '%s\n' '--- local flag behavior probe (using installed tsx if present) ---'
if command -v tsx >/dev/null 2>&1; then
tmpdir="$(mktemp -d)"
cat > "$tmpdir/probe.ts" <<'TS'
console.log("node-options-value:", process.env.SECRET_VAR)
TS
export SECRET_VAR=fromEnv
printf '%s ' 'tsx --env-file=.env --env-file-if-exists=.env: '
tsx --env-file=.env --env-file-if-exists=.env "$tmpdir/probe.ts" 2>&1 || printf '%s\n' '(exited non-zero)'
rm -rf "$tmpdir"
fiRepository: Mayank9056-MM/ctf-silk-project
Length of output: 729
Use --env-file-if-exists=.env for these seed scripts.
Node throws ENOENT for a missing --env-file target, while these scripts already import "dotenv/config", which loads .env when present and stays silent when it is not. Replace tsx --env-file=.env with tsx --env-file-if-exists=.env, or drop the flag if the project does not rely on Node-level env-file preprocessing.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@package.json` around lines 10 - 17, Update the seed script commands (seed,
seed:event, seed:chapters, seed:story, seed:challenges, seed:users, and
seed:admin) to use tsx --env-file-if-exists=.env instead of the strict
--env-file=.env flag, preserving their existing script paths and behavior.
| "title" VARCHAR(100) NOT NULL, | ||
| "content" TEXT NOT NULL, | ||
| "level" "HintLevel" NOT NULL, | ||
| "xpCost" INTEGER NOT NULL DEFAULT 0, |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Expect: all write paths reject negative XP values, or the schema adds checks.
rg -n -C 4 '\bxpCost\b|\bxpSpent\b' --glob '*.{ts,tsx,js,jsx,sql,prisma}'Repository: Mayank9056-MM/ctf-silk-project
Length of output: 168
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "tracked files around prisma:"
git ls-files | sed -n '/prisma/p' | head -200
echo
echo "migration file:"
if [ -f prisma/migrations/20260804192440_add_hint/migration.sql ]; then
cat -n prisma/migrations/20260804192440_add_hint/migration.sql
fi
echo
echo "schema files:"
fd -a 'schema\.prisma$|.*\.prisma$' prisma . 2>/dev/null | sed 's#^\./##' | head -100
echo
echo "search xpCost/xpSpent broader:"
rg -n -C 4 '\bxpCost\b|\bxpSpent\b' . || trueRepository: Mayank9056-MM/ctf-silk-project
Length of output: 16180
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "hint service outline:"
ast-grep outline modules/hint -f '.*service.*|.*guard.*|.*resolver.*' || true
echo
echo "search xpCost/xpSpent in module code with context:"
rg -n -C 8 '\bxpCost\b|\bxpSpent\b' modules prisma || true
echo
echo "database CHECK constraints in migrations:"
rg -n 'CHECK\s*\(|Check\s*\(|constraint|`@unique`|`@id`' --glob '*.sql' prisma/migrations || trueRepository: Mayank9056-MM/ctf-silk-project
Length of output: 22144
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "files likely containing Hint/PlayerHint services or seeds:"
git ls-files | rg '(^modules/hint/seed|hint\.service|hint\.db|hint\.repository|seed-hints|hint.constants)' || true
echo
echo "hints service outline:"
fd -a 'hint\.service\.(tsx?|ts)' . --owner --no-ignore 2>/dev/null | while read -r f; do
echo "--- $f"
wc -l "$f"
done
echo
echo "seed/constants relevant sections:"
for f in modules/hint/constants/hint.constants.ts modules/hint/seed-hints.ts; do
if [ -f "$f" ]; then
echo "--- $f lines 80-110"
sed -n '80,110p' "$f" | cat -n -v
fi
done
echo
echo "write/repo references to hints model fields:"
python3 - <<'PY'
from pathlib import Path
import re
terms = ['xpCost', 'xpSpent', 'PlayerHint', 'hint.xpCost', 'hintSpent', 'HINT_XP_COST_CHANGED']
for p in Path('.').rglob('*'):
if not p.is_file(): continue
if p.parts and p.parts[0] in {'.git','node_modules','dist','build','coverage'}: continue
try:
txt = p.read_text(errors='ignore')
except Exception:
continue
if any(t in txt for t in terms) or 'hints.update' in txt or 'createPlayerHint' in txt:
print(f"--- {p}")
for i,line in enumerate(txt.splitlines(),1):
if any(t in line for t in terms) or 'hints.update' in line or 'createPlayerHint' in line:
print(f"{i}:{line[:220]}")
PYRepository: Mayank9056-MM/ctf-silk-project
Length of output: 392
Enforce non-negative XP fields at the database layer.
xpCost and xpSpent are signed integers with only a default of 0. Treat negative hint costs or negative unlock spend as invalid and add hints.xpCost >= 0 and player_hints.xpSpent >= 0 constraints so future inserts or updates cannot store bad state.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@prisma/migrations/20260804192440_add_hint/migration.sql` at line 11, Update
the migration schema to enforce non-negative values for both hints.xpCost and
player_hints.xpSpent by adding database CHECK constraints requiring each field
to be greater than or equal to zero, while preserving their existing INTEGER NOT
NULL DEFAULT 0 definitions.
| -- AddForeignKey | ||
| ALTER TABLE "hints" ADD CONSTRAINT "hints_challengeId_fkey" FOREIGN KEY ("challengeId") REFERENCES "challenges"("id") ON DELETE CASCADE ON UPDATE CASCADE; | ||
|
|
||
| -- AddForeignKey | ||
| ALTER TABLE "player_hints" ADD CONSTRAINT "player_hints_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE; | ||
|
|
||
| -- AddForeignKey | ||
| ALTER TABLE "player_hints" ADD CONSTRAINT "player_hints_hintId_fkey" FOREIGN KEY ("hintId") REFERENCES "hints"("id") ON DELETE CASCADE ON UPDATE CASCADE; |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Expect: deletion behavior explicitly treats player hint records as disposable or historical.
rg -n -C 5 '\b(playerHints|PlayerHint|xpSpent|unlockedAt)\b|\.delete(Many)?\(' \
--glob '*.{ts,tsx,js,jsx,sql,prisma}'Repository: Mayank9056-MM/ctf-silk-project
Length of output: 168
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "Tracked files sample:"
git ls-files | sed -n '1,120p'
echo
echo "Migration file:"
cat -n prisma/migrations/20260804192440_add_hint/migration.sql | sed -n '1,120p'
echo
echo "Search for hints/player hints/xp/unlocked wording:"
rg -n -C 3 'hints|player_hints|playerHints|PlayerHint|xpSpent|XP|XP spent|XP spent|unlockedAt|unlocked|xpSpent|xp_spent' -S . || true
echo
echo "Prisma schema:"
fd -a 'schema.prisma|*.prisma' . -d 3 -x sh -c 'echo "--- {}"; cat -n "{}" | sed -n "1,220p"'Repository: Mayank9056-MM/ctf-silk-project
Length of output: 50386
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "Schema file locations:"
fd -a 'schema\.prisma|\.prisma$' . -d 4
echo
echo "Prisma schema:"
SCHEMA="$(fd 'schema\.prisma|.*\.prisma' prisma -d 4 | head -n1)"
[ -n "${SCHEMA:-}" ] && cat -n "${SCHEMA}" | sed -n '1,260p' || true
echo
echo "Hint module services/repositories repository/files:"
fd -a '\.(ts|tsx)$' modules/lib/prisma prisma -d 4 | sort | sed -n '1,220p'
echo
echo "Hint service outline:"
ast-grep outline modules/hint/services/hint.service.ts --view expanded || true
echo
echo "Hint service relevant sections:"
cat -n modules/hint/services/hint.service.ts | sed -n '1,260p'
echo
echo "Hint repository relevant sections:"
cat -n modules/hint/repositories/hint.repository.ts | sed -n '1,220p' || true
echo
echo "Hint DTO mapper relevant sections:"
cat -n modules/hint/mapper/hint.mapper.ts | sed -n '1,180p' || true
echo
echo "Focused searches for hint deletion/update/admin usage:"
rg -n -C 4 '\b(hint|Hint|Hints)\b|\.(delete|destroy|remove|updateMany|updateOne|create|unlock|xpSpent|unlockedAt)\b|deleteMany\(|deleteOne\(' modules/hint scripts package.json prisma -g '*.ts' -g '*.tsx' -g '*.sql' -g '*.prisma' -g 'package.json' || trueRepository: Mayank9056-MM/ctf-silk-project
Length of output: 7676
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "Prisma schema Hint/PlayerHint models:"
cat -n prisma/schema.prisma | sed -n '260,420p'
echo
echo "Hint module files:"
find modules/hint -maxdepth 3 -type f \( -name '*.ts' -o - name '*.tsx' \) | sort
echo
echo "Hint module relevant files:"
for f in modules/hint/constants/hint.constants.ts modules/hint/constants/hint.events.ts modules/hint/repositories/hint.repository.ts modules/hint/services/hint.service.ts modules/hint/mapper/hint.mapper.ts modules/hint/types/hint.types.ts; do
if [ -f "$f" ]; then
echo "--- $f"
cat -n "$f" | sed -n '1,260p'
fi
done
echo
echo "Focused references to hint unlock/history/XP:"
rg -n -C 4 'hint|Hint|PlayerHint|xpSpent|xpCost|unlockedAt|unlock' modules/hint modules -g '*.ts' -g '*.tsx' -g '*.sql' -g '*.prisma' || trueRepository: Mayank9056-MM/ctf-silk-project
Length of output: 5290
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "Hint/PlayerHint model definitions:"
python3 - <<'PY'
from pathlib import Path
p=Path('prisma/schema.prisma')
text=p.read_text()
for i,line in enumerate(text.splitlines(),1):
if 'model Hint' in line or 'model PlayerHint' in line:
start=max(1,i-5)
print(f'--- lines {start}-{i+25} ---')
lines=text.splitlines()
for n in range(start, min(len(lines), i+25)+1):
print(f'{n:4d}: {lines[n-1]}')
PY
echo
echo "Hint module files:"
find modules/hint -maxdepth 3 -type f | sort
echo
echo "Hint module service/repo/mapper if present:"
for f in modules/hint/services/hint.service.ts modules/hint/repositories/hint.repository.ts modules/hint/mapper/hint.mapper.ts; do
if [ -f "$f" ]; then
echo "--- $f"
cat -n "$f" | sed -n '1,260p'
fi
done
echo
echo "Focused references to PlayerHint/unlock/XP/status change:"
rg -n -C 4 'PlayerHint|PlayerHint|playerHints|xpSpent|xpcost|xpcost|xpcost|unlock|hintId|hintId|status|DRAFT|ARCHIVED|onDelete|`@Relation`|hints' modules/hint modules prisma -g '*.ts' -g '*.tsx' -g '*.sql' -g '*.prisma' -g '!prisma/migrations/**/migration.sql' | sed -n '1,260p' ... || trueRepository: Mayank9056-MM/ctf-silk-project
Length of output: 1900
Make the PlayerHint cascade policy intentional.
PlayerHint records unlockedAt and xpSpent, so onDelete: Cascade deletes a user’s unlock history when the User is deleted and deletes unlock history when a Hint is deleted. If this data is player progression/accounting history, change this before this model is used: use RESTRICT, soft-delete, or archive the row instead of erasing it.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@prisma/migrations/20260804192440_add_hint/migration.sql` around lines 38 -
45, Update the PlayerHint foreign-key cascade policies in the migration,
specifically player_hints_userId_fkey and player_hints_hintId_fkey, so deleting
a User or Hint does not erase unlock history; use the project’s intentional
preservation strategy, such as RESTRICT, soft deletion, or archiving, while
leaving the hints_challengeId_fkey relationship unchanged.
| async function confirm(): Promise<boolean> { | ||
| if (process.env.CI === "true" || process.argv.includes("--yes")) { | ||
| // CI and explicit --yes both skip the interactive prompt — CI has | ||
| // no stdin to read from, and --yes is an intentional opt-out for | ||
| // scripted local use (a Makefile target, a pre-test setup script). | ||
| return true; | ||
| } | ||
|
|
||
| const rl = readline.createInterface({ | ||
| input: process.stdin, | ||
| output: process.stdout, | ||
| }); | ||
|
|
||
| console.log( | ||
| "\n⚠️ This will PERMANENTLY DELETE all data in the configured database, then re-migrate and re-seed it.", | ||
| ); | ||
| const answer = await rl.question( | ||
| `Type "${CONFIRMATION_PHRASE}" to continue: `, | ||
| ); | ||
| rl.close(); | ||
|
|
||
| return answer.trim() === CONFIRMATION_PHRASE; | ||
| } | ||
|
|
||
| /** | ||
| * Three distinct, individually-loggable steps rather than one combined | ||
| * shell command — if `migrate reset` succeeds but `generate` fails | ||
| * (a stale node_modules, a version mismatch), the failure is | ||
| * unambiguous about which stage broke, rather than a single opaque | ||
| * combined-command exit code. | ||
| */ | ||
| async function resetDatabase(): Promise<void> { | ||
| const confirmed = await confirm(); | ||
| if (!confirmed) { | ||
| console.log("[reset-db] Aborted — confirmation phrase did not match."); | ||
| process.exitCode = 1; | ||
| return; | ||
| } | ||
|
|
||
| console.log("\n[reset-db] → prisma migrate reset"); | ||
| execSync("npx prisma migrate reset --force --skip-seed", { | ||
| stdio: "inherit", | ||
| }); |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Description: Check whether CI invokes db:reset and how DATABASE_URL is supplied.
fd -H -e yml -e yaml . .github --exec rg -n 'db:reset|DATABASE_URL|prisma migrate' {} \; 2>/dev/null || echo "no workflow references found"
rg -n 'DATABASE_URL' --hidden -g '!node_modules' -g '!*.lock'Repository: Mayank9056-MM/ctf-silk-project
Length of output: 197
🏁 Script executed:
#!/bin/bash
set -u
printf 'Tracked files matching reset/db/seed/env docs: '
git ls-files | rg '(^scripts/reset-db\.ts$|(^|/)(package\.json|pnpm-lock\.yaml|yarn.lock|package-lock\.json|Makefile|.*\.env.*|.*\.test\.ts|.*\.spec\.ts).*|(^|/)\.github/workflows/)'
printf '\nscripts/reset-db.ts excerpt:\n'
sed -n '1,140p' scripts/reset-db.ts
printf '\npackage scripts and Prisma version:\n'
if [ -f package.json ]; then jq '.scripts, .devDependencies, .dependencies' package.json; fi
printf '\nRead-only structural/static verifier for reset-db confirmation bypass and database target:\n'
python3 - <<'PY'
from pathlib import Path
p = Path('scripts/reset-db.ts')
s = p.read_text()
checks = {
"has_confirm": "async function confirm()" in s,
"ci_bypass": 'process.env.CI === "true"' in s,
"yes_bypass": 'process.argv.includes("--yes")' in s,
"bypass_returns_true": 'process.env.CI === "true" || process.argv.includes("--yes")' in s and "return true;" in s[s.index('process.argv.includes("--yes")'):s.index('process.argv.includes("--yes")')+500],
"migrate_reset_force": 'execSync("npx prisma migrate reset --force --skip-seed"' in s,
"uses_DATABASE_URL_via_prisma": 'DATABASE_URL' in s or 'prismaClient' in s or '.dotenv/config' in s,
}
for k, v in checks.items():
print(f"{k}={v}")
print("function reset starts after confirm", s.find('async function resetDatabase') > s.find('async function confirm'))
PY
printf '\nAny explicit guard against CI/production target in reset-db:\n'
rg -n 'CI|NPM|NODE_ENV|DATABASE_URL|production|refus|env=' scripts/reset-db.ts package.json .github 2>/dev/null || trueRepository: Mayank9056-MM/ctf-silk-project
Length of output: 5179
🌐 Web query:
Prisma migrate reset --force --skip-seed deletes all tables configured DATABASE_URL documentation
💡 Result:
Yes, the prisma migrate reset command effectively deletes all tables configured in your database by dropping the entire database or schema (depending on the database provider) and then recreating it [1][2][3]. Key details regarding your query: 1. Data Loss: The command is destructive and results in the loss of all existing data in the database defined by your DATABASE_URL [1][4][5]. It is intended strictly for development environments [1][2][3]. 2. --force: This flag is used to skip the interactive confirmation prompt that normally appears before the destructive action is taken [1][2]. 3. --skip-seed: In earlier versions of Prisma (v6 and older), this flag was used to prevent the automatic execution of seed scripts after the database was reset [2][6]. However, please note that in Prisma ORM v7 and later, automatic seeding during this process has been removed entirely, and seeding must now be triggered explicitly using npx prisma db seed [6]. If you are using a version of Prisma where --skip-seed is still supported, it will prevent the execution of your seed scripts, but the underlying database tables will still be dropped and recreated as part of the reset process [1][2]. Because this command acts on the entire database or schema specified in your configuration, it will remove all tables within that scope, regardless of whether they are managed by Prisma [1][2][3].
Citations:
- 1: https://www.prisma.io/docs/cli/migrate/reset
- 2: https://www.prisma.io/docs/orm/reference/prisma-cli-reference
- 3: https://www.prisma.io/docs/orm/prisma-migrate/workflows/development-and-production
- 4: https://www.prisma.io/docs/cli/migrate
- 5: https://www.prisma.io/docs/orm/prisma-migrate/understanding-prisma-migrate/limitations-and-known-issues
- 6: https://www.prisma.io/docs/v6/orm/prisma-migrate/workflows/seeding
Authorization Bypass (CWE-863): Incorrect Authorization
Reachability: Internal
Reachability path
● Entry
scripts/reset-db.ts:56
resetDatabase
│
▼
● Hop
scripts/seed.ts
│
▼
● Sink
scripts/seed-challenges.ts
Add a target database check before the destructive reset.
confirm() returns true for CI === "true" or --yes, so db:reset can skip the confirmation prompt with a non-development DATABASE_URL. prisma migrate reset --force --skip-seed drops the configured database, and Prisma reset is only intended for development.
Gate the destructive path on the target environment before calling it.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@scripts/reset-db.ts` around lines 25 - 67, Add a target-environment
validation in resetDatabase before confirm() or prisma migrate reset runs,
rejecting non-development DATABASE_URL targets even when CI or --yes bypasses
confirmation. Reuse the existing environment/configuration symbols to identify
the development database, set a failure exit code, and return without invoking
the destructive reset.
| console.log("\n[reset-db] → seed.ts"); | ||
| await runSeed(); | ||
|
|
||
| console.log("\n[reset-db] Database reset and seeded."); | ||
| } | ||
|
|
||
| if (require.main === module) { | ||
| resetDatabase().catch((error) => { | ||
| console.error("\n[reset-db] Failed:", error); | ||
| process.exitCode = 1; | ||
| }); | ||
| } No newline at end of file |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
Disconnect Prisma after runSeed().
resetDatabase imports main from ./seed and calls it directly. scripts/seed.ts disconnects only inside its own require.main branch, which does not run here. The Prisma connection pool therefore stays open and can keep the event loop alive after a successful reset, so the command hangs instead of exiting.
🔒️ Proposed fix
+import prisma from "`@/lib/prisma`";
import { execSync } from "node:child_process"; if (require.main === module) {
- resetDatabase().catch((error) => {
- console.error("\n[reset-db] Failed:", error);
- process.exitCode = 1;
- });
+ resetDatabase()
+ .catch((error) => {
+ console.error("\n[reset-db] Failed:", error);
+ process.exitCode = 1;
+ })
+ .finally(() => prisma.$disconnect());
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| console.log("\n[reset-db] → seed.ts"); | |
| await runSeed(); | |
| console.log("\n[reset-db] Database reset and seeded."); | |
| } | |
| if (require.main === module) { | |
| resetDatabase().catch((error) => { | |
| console.error("\n[reset-db] Failed:", error); | |
| process.exitCode = 1; | |
| }); | |
| } | |
| console.log("\n[reset-db] → seed.ts"); | |
| await runSeed(); | |
| console.log("\n[reset-db] Database reset and seeded."); | |
| } | |
| if (require.main === module) { | |
| resetDatabase() | |
| .catch((error) => { | |
| console.error("\n[reset-db] Failed:", error); | |
| process.exitCode = 1; | |
| }) | |
| .finally(() => prisma.$disconnect()); | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@scripts/reset-db.ts` around lines 72 - 83, Update resetDatabase to explicitly
disconnect Prisma after runSeed() completes, reusing the existing Prisma
client/import used by the seed flow. Ensure disconnection occurs after
successful seeding so the reset command can exit cleanly, while preserving the
existing failure handling.
| await prisma.user.upsert({ | ||
| where: { email }, | ||
| update: { | ||
| // Re-running with a new SEED_ADMIN_PASSWORD rotates the bootstrap | ||
| // credential — the one intentional exception to "seeds don't | ||
| // overwrite existing data," since a lost/compromised admin | ||
| // password needs a way to be reset via this same script. | ||
| passwordHash, | ||
| }, | ||
| create: { | ||
| username, | ||
| email, | ||
| fullName: "Platform Administrator", | ||
| passwordHash, | ||
| role: Role.SUPER_ADMIN, | ||
| status: UserStatus.ACTIVE, | ||
| }, | ||
| }); | ||
|
|
||
| console.log(`[seed-admin] Admin account ready (${email}).`); |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
The update branch never grants SUPER_ADMIN.
If a User row already exists with SEED_ADMIN_EMAIL and a lower role, this upsert rotates that user's password and leaves the role unchanged. The script then logs "Admin account ready" while the platform still has no administrator. A disabled admin row has the same problem, because status is not restored.
🛡️ Proposed fix to make the admin grant authoritative
update: {
// Re-running with a new SEED_ADMIN_PASSWORD rotates the bootstrap
// credential — the one intentional exception to "seeds don't
// overwrite existing data," since a lost/compromised admin
// password needs a way to be reset via this same script.
passwordHash,
+ role: Role.SUPER_ADMIN,
+ status: UserStatus.ACTIVE,
},📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| await prisma.user.upsert({ | |
| where: { email }, | |
| update: { | |
| // Re-running with a new SEED_ADMIN_PASSWORD rotates the bootstrap | |
| // credential — the one intentional exception to "seeds don't | |
| // overwrite existing data," since a lost/compromised admin | |
| // password needs a way to be reset via this same script. | |
| passwordHash, | |
| }, | |
| create: { | |
| username, | |
| email, | |
| fullName: "Platform Administrator", | |
| passwordHash, | |
| role: Role.SUPER_ADMIN, | |
| status: UserStatus.ACTIVE, | |
| }, | |
| }); | |
| console.log(`[seed-admin] Admin account ready (${email}).`); | |
| await prisma.user.upsert({ | |
| where: { email }, | |
| update: { | |
| // Re-running with a new SEED_ADMIN_PASSWORD rotates the bootstrap | |
| // credential — the one intentional exception to "seeds don't | |
| // overwrite existing data," since a lost/compromised admin | |
| // password needs a way to be reset via this same script. | |
| passwordHash, | |
| role: Role.SUPER_ADMIN, | |
| status: UserStatus.ACTIVE, | |
| }, | |
| create: { | |
| username, | |
| email, | |
| fullName: "Platform Administrator", | |
| passwordHash, | |
| role: Role.SUPER_ADMIN, | |
| status: UserStatus.ACTIVE, | |
| }, | |
| }); | |
| console.log(`[seed-admin] Admin account ready (${email}).`); |
🧰 Tools
🪛 ast-grep (0.45.0)
[warning] 56-56: Avoid logging sensitive data
Context: console.log([seed-admin] Admin account ready (${email}).)
Note: [CWE-532] Insertion of Sensitive Information into Log File.
(log-sensitive-data-typescript)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@scripts/seed-admin.ts` around lines 38 - 57, Update the existing-user branch
of the upsert in the seed-admin flow so the account is always restored to the
bootstrap administrator state: set role to Role.SUPER_ADMIN and status to
UserStatus.ACTIVE alongside passwordHash. Keep the create branch and existing
email-based lookup unchanged.
| await prisma.challenge.upsert({ | ||
| where: { | ||
| chapterId_displayOrder: { chapterId, displayOrder: challenge.displayOrder }, | ||
| }, | ||
| update: { | ||
| title: challenge.title, | ||
| chapterId: challenge.chapterId, | ||
| displayOrder: challenge.displayOrder, | ||
| difficulty: challenge.difficulty, | ||
| xpReward: challenge.xpReward, | ||
| flagHash, | ||
| }, | ||
| create: { | ||
| chapterId, | ||
| slug: challenge.slug, | ||
| title: challenge.title, | ||
| chapterId: challenge.chapterId, | ||
| displayOrder: challenge.displayOrder, | ||
| difficulty: challenge.difficulty, | ||
| xpReward: challenge.xpReward, | ||
| displayOrder: challenge.displayOrder, | ||
| flagHash, | ||
| }, | ||
| select: { id: true }, | ||
| }); |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win
update omits slug, so slug edits never reach existing rows.
The upsert keys on (chapterId, displayOrder). If you change slug in CHALLENGE_CONTENT and re-run, the existing row keeps the old slug. The seed data and the database then disagree on the public identifier.
♻️ Proposed fix to keep slug in sync
update: {
+ slug: challenge.slug,
title: challenge.title,
difficulty: challenge.difficulty,
flagHash,
},📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| await prisma.challenge.upsert({ | |
| where: { | |
| chapterId_displayOrder: { chapterId, displayOrder: challenge.displayOrder }, | |
| }, | |
| update: { | |
| title: challenge.title, | |
| chapterId: challenge.chapterId, | |
| displayOrder: challenge.displayOrder, | |
| difficulty: challenge.difficulty, | |
| xpReward: challenge.xpReward, | |
| flagHash, | |
| }, | |
| create: { | |
| chapterId, | |
| slug: challenge.slug, | |
| title: challenge.title, | |
| chapterId: challenge.chapterId, | |
| displayOrder: challenge.displayOrder, | |
| difficulty: challenge.difficulty, | |
| xpReward: challenge.xpReward, | |
| displayOrder: challenge.displayOrder, | |
| flagHash, | |
| }, | |
| select: { id: true }, | |
| }); | |
| await prisma.challenge.upsert({ | |
| where: { | |
| chapterId_displayOrder: { chapterId, displayOrder: challenge.displayOrder }, | |
| }, | |
| update: { | |
| slug: challenge.slug, | |
| title: challenge.title, | |
| difficulty: challenge.difficulty, | |
| flagHash, | |
| }, | |
| create: { | |
| chapterId, | |
| slug: challenge.slug, | |
| title: challenge.title, | |
| difficulty: challenge.difficulty, | |
| displayOrder: challenge.displayOrder, | |
| flagHash, | |
| }, | |
| }); |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@scripts/seed-challenges.ts` around lines 62 - 79, Update the update payload
in the challenge.upsert call to include challenge.slug alongside the other
mutable challenge fields, so existing rows keyed by chapterId and displayOrder
synchronize slug changes while preserving the current create behavior.
| for (const chapter of CHAPTERS) { | ||
| await prisma.chapter.upsert({ | ||
| where: { order: chapter.number }, | ||
| update: { | ||
| slug: chapter.slug, | ||
| title: chapter.title, | ||
| }, | ||
| create: { | ||
| slug: chapter.slug, | ||
| title: chapter.title, | ||
| order: chapter.number, | ||
| status: ContentStatus.PUBLISHED, | ||
| }, | ||
| }); |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win
The documented upsert key does not match the code.
The doc comment states the upsert happens by slug, but line 38 keys on order. This compiles only if Chapter.order is unique. It also rewrites slug on an order-keyed row, which fails if slug is unique and two chapters swap slugs.
Align the comment with the key that the schema actually enforces.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@scripts/seed-chapters.ts` around lines 36 - 49, Update the documentation for
the chapter seeding loop to state that the upsert is keyed by chapter order,
matching the `where: { order: chapter.number }` in the `CHAPTERS` iteration; do
not change the upsert implementation.
| /** | ||
| * Intentional no-op. | ||
| * | ||
| * Hints/Advisor is listed as a "Future module" — no Hint or Advisor | ||
| * Prisma model exists yet (the project's own architecture rule: never | ||
| * build schema or seed infrastructure ahead of a real, scheduled | ||
| * feature — vocabulary is fine, tables are not). | ||
| * | ||
| * This file exists only so seed.ts's step list and package.json's | ||
| * `seed:hints` script don't need to change the moment the Hints module | ||
| * actually lands. Replace this body with real seed logic then — not | ||
| * before, and not by inventing a model shape now to fill the gap. | ||
| */ | ||
| export async function seedHints(): Promise<void> { | ||
| console.log(" (skipped — no Hint model exists yet)"); | ||
| } No newline at end of file |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
The stated reason for the no-op contradicts this PR's migration.
This PR adds prisma/migrations/20260804192440_add_hint/migration.sql, which creates hint levels, hint records, and player hint records. The comment claims no Hint model exists yet. The comment also references a seed:hints script, which the provided package.json does not define.
Update the comment to state the real reason for the no-op, or implement the seeder against the new hint models.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@scripts/seed-hints.ts` around lines 1 - 16, Update seedHints so its
documentation matches the repository state: remove the claim that no Hint model
or migration exists and the unsupported seed:hints script reference, then state
the actual reason the function remains a no-op. If no valid reason exists,
implement seeding using the newly added hint-related Prisma models instead.
Summary by Sourcery
Refactor database seeding into modular scripts with clear responsibilities and add infrastructure for hints and player hint tracking.
New Features:
Enhancements:
Build:
Summary by CodeRabbit
New Features
Bug Fixes
Chores