Skip to content

Pull request from feat/mob-29-create-workspace - #4

Closed
nusteam wants to merge 13 commits into
mainfrom
feat/mob-29-create-workspace
Closed

nusteam wants to merge 13 commits into
mainfrom
feat/mob-29-create-workspace

Conversation

@nusteam

@nusteam nusteam commented Jul 30, 2026 •

Copy link
Copy Markdown
Contributor

Summary by CodeRabbit

  • New Features

    • Added research Spaces: create, search, sort, and view source and note counts.
    • Added refresh-token authentication, token rotation, and logout support.
    • Added improved sign-up and login validation, including password confirmation.
    • Added configurable CORS support and expanded API documentation.
  • Bug Fixes

    • Improved authentication error handling for expired, invalid, and revoked tokens.
    • Sensitive account details are no longer included in user responses.
  • Changes

    • Legacy posts and comments endpoints have been removed.

@coderabbitai

coderabbitai Bot commented Jul 30, 2026 •

Copy link
Copy Markdown

Review Change Stack

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a1e54e4f-e456-4ac9-9cb4-2e350b877640

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The PR replaces the legacy post/comment model with research spaces, adds access/refresh token authentication with rotation and revocation, updates API documentation and middleware, and configures Prisma, path aliases, linting, formatting, and build tooling.

Changes

Folio API migration

Layer / File(s) Summary
Research domain and persistence foundation
src/prisma/schema.prisma, src/prisma/migrations/*, src/prisma/repositories/*, src/prisma/prisma.client.ts, src/prisma/seeds/*, src/api/utils/constants.ts
The Prisma schema and migrations add research-space entities, token fields, relationships, shared client initialization, user repository mutations, and seeded research spaces.
Token-based authentication flow
src/api/services/auth.service.ts, src/api/utils/token.util.ts, src/api/middlewares/auth.middleware.ts, src/api/controllers/auth.controller.ts, src/api/routes/auth.router.ts, src/api/routes/validators/auth.validator.ts, src/config/enviroment.ts
Authentication now issues access and refresh tokens, hashes and rotates refresh tokens, checks token versions, and exposes refresh and logout routes with structured error codes.
Research-space API flow
src/api/types/space.ts, src/api/services/space.service.ts, src/prisma/repositories/space.repository.ts, src/api/controllers/space.controller.ts, src/api/routes/space.router.ts, src/api/routes/validators/space.validator.ts
Authenticated space listing and creation support search, sorting, validation, owner-scoped duplicate checks, and source/note counts.
API contracts and middleware
src/api/docs/openapi.ts, src/api/index.ts, src/api/middlewares/*, src/api/routes/response.ts, src/api/controllers/user.controller.ts, src/api/services/user.service.ts, src/api/routes/user.router.ts, src/config/cors.ts
OpenAPI definitions, response envelopes, user ID handling, error responses, CORS, request logging, and validation use the updated API contracts and centralized constants.
Build and development wiring
.prettierrc.json, eslint.config.js, .yarnrc.yml, package.json, tsconfig.json, .gitignore
Formatting, linting, Yarn, dependency scripts, TypeScript aliases, and generated-output ignores are configured for the revised source tree.

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title is just the source branch name and does not describe the actual workspace/auth/schema changes. Use a concise title that names the main change, such as "Add workspace support and token-based auth".
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 18

🧹 Nitpick comments (7)
src/prisma/seeds/space.seed.ts (1)

53-65: 📐 Maintainability & Code Quality | 🔵 Trivial

Reseeding won't update existing space content.

update: {} means if the seed's name/researchObjective values change later, re-running the script against an already-seeded DB leaves stale rows untouched. Consider populating update with the same fields if idempotent refresh is desired.
[optional_refactor_low_reward_skip_consideration]

🤖 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 `@src/prisma/seeds/space.seed.ts` around lines 53 - 65, Update the
researchSpace.upsert call so its update branch refreshes existing records with
the seed-controlled name and researchObjective values, while preserving the
current create fields and stable seed-space-${i} identity.
src/prisma/migrations/20260730035148_add_token_version_field_to_user/migration.sql (1)

1-19: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Same schema-churn concern as the init migration.

This migration drops authRef (added one day earlier) and re-adds password as NOT NULL without a default — confirming these two migrations should be squashed together rather than shipped as sequential, self-reversing steps. See the comment on 20260729070404_init_database/migration.sql.

🤖 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
`@src/prisma/migrations/20260730035148_add_token_version_field_to_user/migration.sql`
around lines 1 - 19, Squash the changes from migration
20260730035148_add_token_version_field_to_user into the preceding init migration
instead of shipping sequential schema changes. Remove the standalone migration’s
drop of User.authRef and re-addition of User.password, and ensure the
consolidated migration creates the final User schema directly while preserving
refreshToken uniqueness and tokenVersion’s default.
src/prisma/migrations/20260729070404_init_database/migration.sql (1)

1-174: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Squash the two User migrations before merging.
src/prisma/migrations/20260729070404_init_database/migration.sql and src/prisma/migrations/20260730035148_add_token_version_field_to_user/migration.sql immediately undo each other on User (authRef ↔ password), so combining them into one coherent migration will avoid churn and a fragile intermediate 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 `@src/prisma/migrations/20260729070404_init_database/migration.sql` around
lines 1 - 174, Consolidate the User changes from the adjacent
add_token_version_field_to_user migration into the init_database migration so
User is transformed directly to its final schema, without the intermediate
authRef/password reversal. Update the User table alteration and related
constraints/indexes to match the final model, then remove the redundant
follow-up migration while preserving all non-User schema changes.

Source: Linters/SAST tools

src/api/services/auth.service.ts (2)

224-235: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Combine the revocation writes into one atomic update.

updateRefreshToken(userId, null) and incrementTokenVersion(userId) are two independent awaited writes. If the second call fails after the first succeeds, the session ends up in a partial state (refresh token cleared but version not bumped) and costs an extra DB round-trip. Since UserRepository.update already accepts an arbitrary Prisma.UserUpdateInput, both fields can be set in a single call.

♻️ Proposed fix
 const logout = async (userId: string) => {
   try {
-    await UserRepository.updateRefreshToken(userId, null);
-    await UserRepository.incrementTokenVersion(userId);
+    await UserRepository.update(userId, {
+      refreshToken: null,
+      tokenVersion: { increment: 1 }
+    });
   } catch {
🤖 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 `@src/api/services/auth.service.ts` around lines 224 - 235, Update logout to
replace the separate UserRepository.updateRefreshToken and
UserRepository.incrementTokenVersion calls with one UserRepository.update
invocation using a Prisma.UserUpdateInput that clears the refresh token and
atomically increments the token version. Preserve the existing error handling
and userId targeting.

88-117: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Minor timing side-channel on login.

Returning immediately when !user (before doing any bcrypt work) versus doing a bcrypt compare when the user exists creates a measurable timing difference between "no such email" and "wrong password" cases, which can aid email enumeration despite identical error messages. Running a dummy bcrypt.compare against a static hash on the not-found path equalizes timing.

🤖 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 `@src/api/services/auth.service.ts` around lines 88 - 117, Update the login
function’s !user path to run bcrypt.compare with the supplied password and a
static dummy hash before throwing the existing INVALID_CREDENTIALS AppError.
Keep the existing user-password comparison for found users and preserve the same
response and error behavior.
src/api/services/user.service.ts (1)

14-20: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

No not-found handling on update; a bad id will surface as a raw exception.

UserRepository.update calls prisma.user.update, which throws (Prisma P2025) if the record doesn't exist. Unlike getOne, this path isn't wrapped into a structured AppError, so callers get an unhandled/raw error instead of a consistent 404 NOT_FOUND response.

🤖 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 `@src/api/services/user.service.ts` around lines 14 - 20, Update the update
function to handle UserRepository.update failures consistently with getOne:
detect Prisma’s record-not-found condition (P2025), convert it to the
established AppError representing a 404 NOT_FOUND response, and preserve
existing behavior for successful updates and unrelated errors.
src/api/index.ts (1)

17-30: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider mounting cors before the body parsers, and gating the docs endpoints.

CORS still functions here, but bodies are parsed for requests from disallowed origins. Moving cors(corsOptions) above express.json() avoids that work. Separately, /api-docs and /api-docs.json are publicly served in every environment — gate them behind env.NODE_ENV !== 'production' (or auth) if the spec isn't meant to be public.

🤖 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 `@src/api/index.ts` around lines 17 - 30, In the Express app setup, move
cors(corsOptions) before express.json() and express.urlencoded() so disallowed
requests are rejected before body parsing. Gate both the /api-docs.json route
and the /api-docs Swagger middleware behind env.NODE_ENV !== 'production',
preserving their current behavior in non-production environments.
🤖 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 @.gitignore:
- Around line 22-23: Update the package.json build/prebuild configuration so the
Prisma generation command runs before tsc and tsc-alias. Reuse the existing
db:generate script, ensuring a clean yarn build creates src/generated/prisma
before compilation.

In @.yarnrc.yml:
- Around line 1-8: Harden the Yarn settings by replacing the unrestricted
approvedGitRepositories pattern with trusted repository patterns, setting
enableScripts to false, and changing npmMinimalAgeGate to a nonzero duration.
Preserve nodeLinker: node-modules.

In `@package.json`:
- Line 7: Update the package.json lint script to quote the src/**/*.ts glob so
the shell passes it unchanged to ESLint, ensuring nested TypeScript files are
discovered consistently.
- Line 6: Update the build script’s tsc-alias invocation to enable full-path
resolution and configure the resolved extension as “.js”, ensuring emitted ESM
imports are compatible with Node’s resolver.

In `@src/api/controllers/user.controller.ts`:
- Around line 6-9: Restrict the getOne controller to self-only access: require
the requested id to be “me” (or otherwise match req.userId) and reject other
user IDs before calling UserService.getOne. Verify the user route is mounted
behind authentication middleware so req.userId is populated, and remove the
unsafe non-null assumption if the project’s auth pattern requires explicit
validation.

In `@src/api/middlewares/auth.middleware.ts`:
- Around line 11-62: Update the auth middleware around verifyAccessToken and
UserRepository.findById so only JWT verification errors are translated into
authentication responses; allow database lookup failures to propagate to the
generic error handler as server errors. Preserve the existing token-expired and
invalid-token handling, while keeping token revocation checks after a successful
user lookup.

In `@src/api/middlewares/request-logger.middleware.ts`:
- Around line 12-17: Update getRequestId to validate that a client-supplied
x-request-id is both within REQUEST_ID_MAX_LENGTH and composed only of a safe
permitted character set, rejecting values containing control or other unsafe
characters and falling back to randomUUID().

In `@src/api/services/auth.service.ts`:
- Around line 44-57: Update the catch around UserRepository.create in the
authentication service to handle only Prisma error code P2002 as
EMAIL_EXISTS/CONFLICT; rethrow all other errors so they propagate as
INTERNAL_ERROR instead of being misclassified as duplicate-email failures.

In `@src/api/services/space.service.ts`:
- Around line 12-29: Update create to rely on the ResearchSpace database
uniqueness constraint for ownerId and name, while retaining the pre-check if
desired. Catch Prisma P2002 unique-violation errors from SpaceRepository.create
and convert them to the same SPACE_NAME_EXISTS AppError with CONFLICT status;
rethrow all other errors unchanged.

In `@src/api/services/user.service.ts`:
- Around line 6-12: Update getOne to catch the not-found error from
UserRepository.findOne, which uses findFirstOrThrow, and translate it into the
existing AppError with StatusCodes.NOT_FOUND instead of relying on an
unreachable null check. Apply the same not-found mapping in update around
prisma.user.update, preserving other errors unchanged.

In `@src/api/utils/constants.ts`:
- Line 3: Update PASSWORD_MIN_LENGTH to 8 in the constants definition so signup
and login validation enforce the documented minimum password length.

In `@src/api/utils/token.util.ts`:
- Around line 63-69: Update verifyAccessToken and verifyRefreshToken to pass the
same issuer, audience, and TOKEN_CONFIG-aligned algorithms: ['HS256']
constraints used when signing tokens. Preserve each function’s existing secret
and payload return behavior while ensuring jwt.verify rejects tokens from other
contexts or using different algorithms.

In `@src/config/cors.ts`:
- Around line 4-12: Update the origin callback in corsOptions to trim each value
produced by env.CORS_ORIGIN.split(',') before comparing it with origin, and
reject disallowed origins via callback(null, false) instead of passing an Error.
Preserve the existing allow behavior for missing or configured origins.

In `@src/config/enviroment.ts`:
- Around line 24-28: Update the token-secret configuration around
JWT_TOKEN_SECRET and REFRESH_TOKEN_SECRET to reject missing or empty values
during startup instead of defaulting to ''. Preserve the existing expiration and
CORS defaults, and add startup validation that the two required secrets are
distinct before authentication proceeds.

In `@src/index.ts`:
- Line 16: Update the startup flow around startApiServer() to explicitly handle
rejected promises from api.server() or awaited initialization, logging the
failure and terminating with a non-zero exit status so the process cannot remain
alive without a listener.

In `@src/prisma/schema.prisma`:
- Around line 24-45: Update the ResearchSpace model to add a composite unique
constraint on ownerId and name, which also provides the needed lookup index for
owner-scoped queries and enforces duplicate-name prevention at the database
level.

In `@src/prisma/seeds/space.seed.ts`:
- Around line 13-16: Update the user creation and reuse log statements around
the seed flow to remove user.email from console output, retaining only the
non-sensitive user.id needed for identification.

In `@tsconfig.json`:
- Around line 3-15: Update the TypeScript module-resolution/build configuration
around module: Preserve so emitted dist files are directly executable by the
package’s Node ESM runtime. Add or configure the required full-path rewrite step
to append .js extensions to extensionless relative imports, or use an
established loader, while preserving the existing ~/* alias resolution.

---

Nitpick comments:
In `@src/api/index.ts`:
- Around line 17-30: In the Express app setup, move cors(corsOptions) before
express.json() and express.urlencoded() so disallowed requests are rejected
before body parsing. Gate both the /api-docs.json route and the /api-docs
Swagger middleware behind env.NODE_ENV !== 'production', preserving their
current behavior in non-production environments.

In `@src/api/services/auth.service.ts`:
- Around line 224-235: Update logout to replace the separate
UserRepository.updateRefreshToken and UserRepository.incrementTokenVersion calls
with one UserRepository.update invocation using a Prisma.UserUpdateInput that
clears the refresh token and atomically increments the token version. Preserve
the existing error handling and userId targeting.
- Around line 88-117: Update the login function’s !user path to run
bcrypt.compare with the supplied password and a static dummy hash before
throwing the existing INVALID_CREDENTIALS AppError. Keep the existing
user-password comparison for found users and preserve the same response and
error behavior.

In `@src/api/services/user.service.ts`:
- Around line 14-20: Update the update function to handle UserRepository.update
failures consistently with getOne: detect Prisma’s record-not-found condition
(P2025), convert it to the established AppError representing a 404 NOT_FOUND
response, and preserve existing behavior for successful updates and unrelated
errors.

In `@src/prisma/migrations/20260729070404_init_database/migration.sql`:
- Around line 1-174: Consolidate the User changes from the adjacent
add_token_version_field_to_user migration into the init_database migration so
User is transformed directly to its final schema, without the intermediate
authRef/password reversal. Update the User table alteration and related
constraints/indexes to match the final model, then remove the redundant
follow-up migration while preserving all non-User schema changes.

In
`@src/prisma/migrations/20260730035148_add_token_version_field_to_user/migration.sql`:
- Around line 1-19: Squash the changes from migration
20260730035148_add_token_version_field_to_user into the preceding init migration
instead of shipping sequential schema changes. Remove the standalone migration’s
drop of User.authRef and re-addition of User.password, and ensure the
consolidated migration creates the final User schema directly while preserving
refreshToken uniqueness and tokenVersion’s default.

In `@src/prisma/seeds/space.seed.ts`:
- Around line 53-65: Update the researchSpace.upsert call so its update branch
refreshes existing records with the seed-controlled name and researchObjective
values, while preserving the current create fields and stable seed-space-${i}
identity.
🪄 Autofix (Beta)

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

Run ID: bed3c7e8-8bd0-4b09-b003-da87e5f85dd2

📥 Commits

Reviewing files that changed from the base of the PR and between 9cad9ad and e73f7f4.

⛔ Files ignored due to path filters (12)
  • src/generated/prisma/browser.ts is excluded by !**/generated/**
  • src/generated/prisma/client.ts is excluded by !**/generated/**
  • src/generated/prisma/commonInputTypes.ts is excluded by !**/generated/**
  • src/generated/prisma/enums.ts is excluded by !**/generated/**
  • src/generated/prisma/internal/class.ts is excluded by !**/generated/**
  • src/generated/prisma/internal/prismaNamespace.ts is excluded by !**/generated/**
  • src/generated/prisma/internal/prismaNamespaceBrowser.ts is excluded by !**/generated/**
  • src/generated/prisma/models.ts is excluded by !**/generated/**
  • src/generated/prisma/models/Comment.ts is excluded by !**/generated/**
  • src/generated/prisma/models/Post.ts is excluded by !**/generated/**
  • src/generated/prisma/models/User.ts is excluded by !**/generated/**
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (55)
  • .env.example
  • .gitignore
  • .prettierrc.json
  • .yarnrc.yml
  • eslint.config.js
  • package.json
  • src/api/controllers/auth.controller.ts
  • src/api/controllers/comment.controller.ts
  • src/api/controllers/post.controller.ts
  • src/api/controllers/space.controller.ts
  • src/api/controllers/user.controller.ts
  • src/api/docs/openapi.ts
  • src/api/errors/app.error.ts
  • src/api/errors/error-codes.ts
  • src/api/index.ts
  • src/api/middlewares/auth.middleware.ts
  • src/api/middlewares/error.middleware.ts
  • src/api/middlewares/request-logger.middleware.ts
  • src/api/middlewares/validation.middleware.ts
  • src/api/routes/auth.router.ts
  • src/api/routes/comment.router.ts
  • src/api/routes/index.ts
  • src/api/routes/post.router.ts
  • src/api/routes/response.ts
  • src/api/routes/space.router.ts
  • src/api/routes/user.router.ts
  • src/api/routes/validators/auth.validator.ts
  • src/api/routes/validators/comment.validator.ts
  • src/api/routes/validators/post.validator.ts
  • src/api/routes/validators/space.validator.ts
  • src/api/routes/validators/user.validator.ts
  • src/api/services/auth.service.ts
  • src/api/services/comment.service.ts
  • src/api/services/post.service.ts
  • src/api/services/space.service.ts
  • src/api/services/user.service.ts
  • src/api/types/space.ts
  • src/api/utils/constants.ts
  • src/api/utils/exclude.ts
  • src/api/utils/token.util.ts
  • src/config/cors.ts
  • src/config/enviroment.ts
  • src/config/logger.ts
  • src/custom.d.ts
  • src/index.ts
  • src/prisma/migrations/20260729070404_init_database/migration.sql
  • src/prisma/migrations/20260730035148_add_token_version_field_to_user/migration.sql
  • src/prisma/prisma.client.ts
  • src/prisma/repositories/comment.repository.ts
  • src/prisma/repositories/post.repository.ts
  • src/prisma/repositories/space.repository.ts
  • src/prisma/repositories/user.repository.ts
  • src/prisma/schema.prisma
  • src/prisma/seeds/space.seed.ts
  • tsconfig.json
💤 Files with no reviewable changes (10)
  • src/api/controllers/post.controller.ts
  • src/api/controllers/comment.controller.ts
  • src/api/routes/post.router.ts
  • src/api/routes/validators/comment.validator.ts
  • src/api/services/post.service.ts
  • src/prisma/repositories/comment.repository.ts
  • src/api/routes/comment.router.ts
  • src/api/routes/validators/post.validator.ts
  • src/api/services/comment.service.ts
  • src/prisma/repositories/post.repository.ts

Comment thread .gitignore
Comment on lines +22 to +23

/src/generated/prisma

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -eu

rg -n '"(build|postinstall|prepare|generate)"|prisma generate' package.json
fd -i 'schema.prisma|prisma.config.ts' -x rg -n 'generator client|output' {}

Repository: nustechnology/Folio-BE

Length of output: 317


🏁 Script executed:

#!/bin/bash
set -eu

printf '\n## package.json\n'
cat -n package.json | sed -n '1,160p'

printf '\n## workflow / ci files\n'
find . -maxdepth 3 \( -path '*/.github/*' -o -name 'azure-pipelines.yml' -o -name 'Jenkinsfile' -o -name 'gitlab-ci.yml' \) -type f | sort | while read -r f; do
  echo "\n### $f"
  cat -n "$f" | sed -n '1,220p'
done

printf '\n## references to db:generate / prisma generate / tsc-alias\n'
rg -n 'db:generate|prisma generate|tsc-alias|generated/prisma|schema.prisma' . -g '!node_modules' -g '!.git'

Repository: nustechnology/Folio-BE

Length of output: 4853


🏁 Script executed:

#!/bin/bash
set -eu

printf '\n## package.json\n'
cat -n package.json | sed -n '1,160p'

printf '\n## workflow / ci files\n'
find . -maxdepth 3 \( -path '*/.github/*' -o -name 'azure-pipelines.yml' -o -name 'Jenkinsfile' -o -name 'gitlab-ci.yml' \) -type f | sort | while read -r f; do
  echo "\n### $f"
  cat -n "$f" | sed -n '1,220p'
done

printf '\n## references to db:generate / prisma generate / tsc-alias / generated/prisma / schema.prisma\n'
rg -n 'db:generate|prisma generate|tsc-alias|generated/prisma|schema.prisma' . -g '!node_modules' -g '!.git'

Repository: nustechnology/Folio-BE

Length of output: 4888


🏁 Script executed:

#!/bin/bash
set -eu

printf '\n## tsconfig files\n'
find . -maxdepth 2 \( -name 'tsconfig*.json' -o -name 'tsconfig.json' \) -type f | sort | while read -r f; do
  echo "\n### $f"
  cat -n "$f" | sed -n '1,220p'
done

printf '\n## generated Prisma files tracked in git\n'
git ls-files 'src/generated/prisma/**' 'dist/generated/prisma/**' 'generated/prisma/**'

printf '\n## source imports that depend on generated Prisma client\n'
rg -n "generated/prisma/client|~/generated/prisma/client" src -g '*.ts'

Repository: nustechnology/Folio-BE

Length of output: 1270


Run Prisma generation before tsc.
src/generated/prisma is a build artifact, but package.json’s build script still goes straight to tsc && tsc-alias. A clean yarn build will fail unless yarn db:generate runs first; add it to the build/prebuild path.

🤖 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 @.gitignore around lines 22 - 23, Update the package.json build/prebuild
configuration so the Prisma generation command runs before tsc and tsc-alias.
Reuse the existing db:generate script, ensuring a clean yarn build creates
src/generated/prisma before compilation.

Comment thread .yarnrc.yml Outdated
Comment on lines +1 to +8
approvedGitRepositories:
- "**"

enableScripts: true

nodeLinker: node-modules

npmMinimalAgeGate: 0

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

set -euo pipefail

printf 'Repo files of interest:\n'
git ls-files .yarnrc.yml package.json yarn.lock .yarn/releases 2>/dev/null || true

printf '\n.yarnrc.yml:\n'
cat -n .yarnrc.yml

printf '\npackage.json (if present):\n'
if [ -f package.json ]; then cat -n package.json; fi

Repository: nustechnology/Folio-BE

Length of output: 2339


🏁 Script executed:

set -euo pipefail

python3 - <<'PY'
from pathlib import Path
for p in [Path('.yarnrc.yml'), Path('package.json')]:
    if p.exists():
        print(f'== {p} ==')
        for i, line in enumerate(p.read_text().splitlines(), 1):
            print(f'{i:4d}: {line}')
        print()
PY

Repository: nustechnology/Folio-BE

Length of output: 2217


🌐 Web query:

Yarn approvedGitRepositories enableScripts npmMinimalAgeGate configuration docs

💡 Result:

These three settings are security-focused configuration options in Yarn (Berry) designed to mitigate supply-chain risks and restrict the execution of arbitrary code [1][2][3]. approvedGitRepositories This setting accepts an array of git repository URL glob patterns [4][5][6]. It acts as an allow-list: Yarn will block any git dependency whose normalized repository URL does not match one of these patterns [4][7]. This is intended to prevent the fetching and installation of dependencies from untrusted or unverified git sources [1][8][3]. enableScripts This boolean setting determines whether Yarn executes lifecycle scripts (such as postinstall) for third-party packages [4][5][7]. When set to false (which is the default in recent versions), Yarn will not run these scripts by default, effectively reducing the risk of malicious code execution during installation [7][2][9]. You can override this behavior on a per-package basis using dependenciesMeta [4][5][10]. npmMinimalAgeGate This setting specifies a minimum age that a package version must reach before it is considered eligible for installation [5][7]. It accepts a duration string (e.g., "1d", "1w") [4][5]. Versions newer than this duration are rejected unless they are explicitly preapproved [5][7]. This mechanism is designed to reduce exposure to supply-chain attacks by delaying the use of newly-published packages until they have been available for a certain period [5][7]. Yarn automatically migrates existing projects to maintain their current configuration even as default security settings become more restrictive [2]. New projects created in recent versions often default to stricter settings for these parameters [1][2].

Citations:


🌐 Web query:

Yarn enableScripts default configuration docs npmMinimalAgeGate approvedGitRepositories

💡 Result:

As of July 30, 2026, the relevant Yarn (Berry) configurations are as follows: enableScripts The enableScripts setting determines whether postinstall scripts are executed during package installation [1][2]. The default value is false [1][2][3]. When disabled, Yarn does not execute postinstall scripts from third-party packages, although workspaces retain the ability to evaluate their own scripts as they are considered trusted [1][2]. You can re-enable scripts globally via.yarnrc.yml or on a per-package basis using dependenciesMeta [1][2]. npmMinimalAgeGate The npmMinimalAgeGate setting specifies the minimum age a package version must have on the npm registry to be considered for installation [4][5]. This acts as a security mechanism to reduce the risk of installing recently published, potentially malicious packages [6][5]. The default value is 0m (zero minutes), meaning the gate is effectively disabled by default unless explicitly configured [4][5]. The value accepts a duration string (e.g., "3d", "1w") or a number [5]. approvedGitRepositories The approvedGitRepositories setting is a security feature that restricts which git repositories Yarn is permitted to clone when resolving git dependencies [7][8]. The default configuration is an empty list ([]), meaning no repositories are allowed by default [7][8]. Projects must explicitly add patterns to this list to allow fetching from specific git repositories [7][8]. Existing projects are generally migrated to include a wildcard in this setting to maintain backward compatibility [3][7].

Citations:


Tighten Yarn supply-chain controls.
approvedGitRepositories: "**" allows any git source, enableScripts: true runs third-party lifecycle scripts globally, and npmMinimalAgeGate: 0 disables the age gate for newly published packages. Restrict Git patterns to trusted repos, keep scripts disabled by default, and use a nonzero age threshold.

🤖 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 @.yarnrc.yml around lines 1 - 8, Harden the Yarn settings by replacing the
unrestricted approvedGitRepositories pattern with trusted repository patterns,
setting enableScripts to false, and changing npmMinimalAgeGate to a nonzero
duration. Preserve nodeLinker: node-modules.

Source: MCP tools

Comment thread package.json Outdated
"scripts": {
"build": "tsc --build --clean && tsc",
"lint": "eslint src/**/*.ts",
"build": "tsc --build --clean && tsc && tsc-alias",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -eu

node -p "JSON.stringify({type: require('./package.json').type, build: require('./package.json').scripts?.build, start: require('./package.json').scripts?.start}, null, 2)"
rg -n 'from ["'\''](~|\.\.?/)' src

Repository: nustechnology/Folio-BE

Length of output: 7004


🏁 Script executed:

#!/bin/bash
set -eu

echo '--- package.json ---'
cat -n package.json

echo
echo '--- tsconfig.json ---'
cat -n tsconfig.json

echo
echo '--- tsconfig files ---'
fd -a 'tsconfig*.json' .

echo
echo '--- references to tsc-alias / resolveFullPaths / moduleResolution / bundler / nodenext ---'
rg -n 'tsc-alias|resolveFullPaths|resolveFullExtension|moduleResolution|nodenext|bundler|ts-node|tsx|node dist|dist/' .

Repository: nustechnology/Folio-BE

Length of output: 5382


🌐 Web query:

tsc-alias resolveFullPaths default ESM .js extension Node loader

💡 Result:

The tsc-alias package provides the resolveFullPaths option to support ECMAScript Module (ESM) requirements in Node.js, specifically the necessity for explicit file extensions in import specifiers [1][2]. When resolveFullPaths is enabled, tsc-alias attempts to identify incomplete import paths (those not ending in a file extension) and replaces them with fully resolved paths (e.g., adding .js) [1][3]. By default, this appends the .js extension, though it can be configured via the resolveFullExtension option to use others like .mjs or .cjs [1][4]. To implement this, you typically configure tsc-alias in your tsconfig.json and update your build script to run tsc-alias after the TypeScript compiler (tsc): 1. Configure tsconfig.json: { "tsc-alias": { "resolveFullPaths": true } } 2. Update build scripts: "scripts": { "build": "tsc && tsc-alias" } Important considerations: - ESM Compatibility: Node.js ESM requires fully qualified paths. tsc-alias performs this transformation as a post-compilation step, making the output code ESM-ready [2][5]. - Not for Runtime: tsc-alias is designed as a build-time tool. It is not intended to be used as a Node.js loader or a runtime resolver (e.g., with ts-node) [6]. - Watch Mode: If you are using watch mode (-w), be aware that running tsc and tsc-alias concurrently can lead to race conditions where tsc-alias attempts to process files that have not yet been generated. A common pattern to resolve this is using a tool like concurrently or ensuring tsc completes before tsc-alias begins processing in a continuous loop [7].

Citations:


Add .js extensions to tsc-alias rewrites. This package is ESM ("type": "module"), and production runs node dist/index.js, so alias rewrites need resolveFullPaths: true with resolveFullExtension: ".js"; otherwise emitted imports can stay extensionless and fail under Node’s resolver.

🤖 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` at line 6, Update the build script’s tsc-alias invocation to
enable full-path resolution and configure the resolved extension as “.js”,
ensuring emitted ESM imports are compatible with Node’s resolver.

Source: MCP tools

Comment thread package.json
"build": "tsc --build --clean && tsc",
"lint": "eslint src/**/*.ts",
"build": "tsc --build --clean && tsc && tsc-alias",
"lint": "eslint src/**/*.ts --fix --no-warn-ignored",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the script in context
git ls-files package.json
cat -n package.json

# Check whether the repo has any npm/yarn/pnpm script config that affects shell parsing
git ls-files | rg '^(package.json|pnpm-workspace\.yaml|yarn\.rc|\.npmrc|package-lock\.json|pnpm-lock\.yaml|yarn\.lock)$' || true

Repository: nustechnology/Folio-BE

Length of output: 2115


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Top-level and nested TypeScript files under src:"
git ls-files 'src/**/*.ts' | sed -n '1,200p'

echo
echo "Directory depths:"
python3 - <<'PY'
import subprocess, os
paths = subprocess.check_output(["git", "ls-files", "src/**/*.ts"], text=True).splitlines()
for p in paths:
    depth = p.count("/")
    print(f"{depth}\t{p}")
PY

echo
echo "Bash expansion without globstar (using repo files only):"
bash -lc 'shopt -u globstar; printf "%s\n" src/**/*.ts 2>/dev/null | sed -n "1,50p"'

echo
echo "Bash expansion with globstar:"
bash -lc 'shopt -s globstar; printf "%s\n" src/**/*.ts 2>/dev/null | sed -n "1,50p"'

Repository: nustechnology/Folio-BE

Length of output: 4055


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Top-level and nested TypeScript files under src:"
git ls-files 'src/**/*.ts' | sed -n '1,200p'

echo
echo "Directory depths:"
python3 - <<'PY'
import subprocess
paths = subprocess.check_output(["git", "ls-files", "src/**/*.ts"], text=True).splitlines()
for p in paths:
    print(f"{p.count('/')}\t{p}")
PY

echo
echo "Bash expansion without globstar:"
bash -lc 'shopt -u globstar; printf "%s\n" src/**/*.ts 2>/dev/null | sed -n "1,50p"'

echo
echo "Bash expansion with globstar:"
bash -lc 'shopt -s globstar; printf "%s\n" src/**/*.ts 2>/dev/null | sed -n "1,50p"'

Repository: nustechnology/Folio-BE

Length of output: 4031


Quote the ESLint glob in package.json. Unquoted src/**/*.ts is expanded by the shell before ESLint runs, so nested files like src/api/routes/validators/*.ts can be skipped in environments where globstar isn’t enabled.

🤖 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` at line 7, Update the package.json lint script to quote the
src/**/*.ts glob so the shell passes it unchanged to ESLint, ensuring nested
TypeScript files are discovered consistently.

Source: MCP tools

Comment on lines 6 to 9
const getOne = async (req: Request, res: Response) => {
const { id } = req.params;
const userId = id === 'me' ? req.userId! : Number(id);
const userId = id === 'me' ? req.userId! : id;
const user = await UserService.getOne(userId);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Any authenticated user can read any other user by ID.

With Number(id) gone, the raw param is forwarded straight to UserService.getOne, so GET /api/v1/users/<other-uuid> returns another account's record (including email per the OpenAPI User schema). If this endpoint is meant to be self-only, restrict it; otherwise return a reduced public projection for non-self lookups.

🔒 Self-only variant
 const getOne = async (req: Request, res: Response) => {
   const { id } = req.params;
-  const userId = id === 'me' ? req.userId! : id;
+  if (id !== 'me' && id !== req.userId) {
+    throw new AppError('Forbidden', StatusCodes.FORBIDDEN);
+  }
+  const userId = req.userId!;
   const user = await UserService.getOne(userId);

Also confirm the auth middleware is mounted on this route, since req.userId! would otherwise be undefined at runtime:

#!/bin/bash
fd -t f 'user.router.ts' -x cat -n
fd -t f 'user.service.ts' -x cat -n
🤖 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 `@src/api/controllers/user.controller.ts` around lines 6 - 9, Restrict the
getOne controller to self-only access: require the requested id to be “me” (or
otherwise match req.userId) and reject other user IDs before calling
UserService.getOne. Verify the user route is mounted behind authentication
middleware so req.userId is populated, and remove the unsafe non-null assumption
if the project’s auth pattern requires explicit validation.

Comment thread src/config/enviroment.ts
Comment on lines +24 to +28
JWT_TOKEN_SECRET: process.env.JWT_TOKEN_SECRET || '',
REFRESH_TOKEN_SECRET: process.env.REFRESH_TOKEN_SECRET || '',
ACCESS_TOKEN_EXPIRATION: process.env.ACCESS_TOKEN_EXPIRATION || '15m',
REFRESH_TOKEN_EXPIRATION: process.env.REFRESH_TOKEN_EXPIRATION || '30d',
CORS_ORIGIN: process.env.CORS_ORIGIN || 'http://localhost:3000'

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Empty-string fallback for token secrets silently weakens auth.

If JWT_TOKEN_SECRET/REFRESH_TOKEN_SECRET are unset, signing/verification proceeds with an empty key instead of failing at boot — tokens become trivially forgeable in any misconfigured deployment. Validate required secrets at startup instead of defaulting them.

🛡️ Suggested fail-fast validation
+const required = (name: string): string => {
+  const value = process.env[name];
+  if (!value) {
+    throw new Error(`Missing required environment variable: ${name}`);
+  }
+  return value;
+};
+
...
-  JWT_TOKEN_SECRET: process.env.JWT_TOKEN_SECRET || '',
-  REFRESH_TOKEN_SECRET: process.env.REFRESH_TOKEN_SECRET || '',
+  JWT_TOKEN_SECRET: required('JWT_TOKEN_SECRET'),
+  REFRESH_TOKEN_SECRET: required('REFRESH_TOKEN_SECRET'),
   ACCESS_TOKEN_EXPIRATION: process.env.ACCESS_TOKEN_EXPIRATION || '15m',
   REFRESH_TOKEN_EXPIRATION: process.env.REFRESH_TOKEN_EXPIRATION || '30d',
   CORS_ORIGIN: process.env.CORS_ORIGIN || 'http://localhost:3000'

Also consider asserting the two secrets differ, so an access token can never be replayed as a refresh token.

📝 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.

Suggested change
JWT_TOKEN_SECRET: process.env.JWT_TOKEN_SECRET || '',
REFRESH_TOKEN_SECRET: process.env.REFRESH_TOKEN_SECRET || '',
ACCESS_TOKEN_EXPIRATION: process.env.ACCESS_TOKEN_EXPIRATION || '15m',
REFRESH_TOKEN_EXPIRATION: process.env.REFRESH_TOKEN_EXPIRATION || '30d',
CORS_ORIGIN: process.env.CORS_ORIGIN || 'http://localhost:3000'
const required = (name: string): string => {
const value = process.env[name];
if (!value) {
throw new Error(`Missing required environment variable: ${name}`);
}
return value;
};
JWT_TOKEN_SECRET: required('JWT_TOKEN_SECRET'),
REFRESH_TOKEN_SECRET: required('REFRESH_TOKEN_SECRET'),
ACCESS_TOKEN_EXPIRATION: process.env.ACCESS_TOKEN_EXPIRATION || '15m',
REFRESH_TOKEN_EXPIRATION: process.env.REFRESH_TOKEN_EXPIRATION || '30d',
CORS_ORIGIN: process.env.CORS_ORIGIN || 'http://localhost:3000'
🤖 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 `@src/config/enviroment.ts` around lines 24 - 28, Update the token-secret
configuration around JWT_TOKEN_SECRET and REFRESH_TOKEN_SECRET to reject missing
or empty values during startup instead of defaulting to ''. Preserve the
existing expiration and CORS defaults, and add startup validation that the two
required secrets are distinct before authentication proceeds.

Comment thread src/index.ts
});
}

startApiServer();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Unhandled rejection from startApiServer() leaves a zombie process.

If api.server() (or anything it awaits) rejects, the failure falls through to the unhandledRejection handler, which only logs — the process stays alive with no listener bound and no non-zero exit for the orchestrator to act on.

🛡️ Suggested fix
-startApiServer();
+startApiServer().catch((error: unknown) => {
+  logger.error('Failed to start API server', {
+    error: error instanceof Error ? error.message : String(error),
+    stack: error instanceof Error ? error.stack : undefined
+  });
+  process.exit(1);
+});
📝 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.

Suggested change
startApiServer();
startApiServer().catch((error: unknown) => {
logger.error('Failed to start API server', {
error: error instanceof Error ? error.message : String(error),
stack: error instanceof Error ? error.stack : undefined
});
process.exit(1);
});
🤖 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 `@src/index.ts` at line 16, Update the startup flow around startApiServer() to
explicitly handle rejected promises from api.server() or awaited initialization,
logging the failure and terminating with a non-zero exit status so the process
cannot remain alive without a listener.

Comment thread src/prisma/schema.prisma
Comment on lines +24 to 45
model ResearchSpace {
id String @id @default(uuid())
ownerId String
owner User @relation(fields: [ownerId], references: [id])
name String
researchObjective String
isArchived Boolean @default(false)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
lastOpenedAt DateTime @default(now())

sources Source[]
conversations Conversation[]
notes Note[]
notebook Notebook?
}

enum OriginType {
UserCreated
SavedAssistantAnswer
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Add an index (and ideally a unique constraint) for ResearchSpace.ownerId/name.

ownerId has no @@index, yet space.service.ts queries by owner on every list/create call (findManyByOwner, findByNameAndOwner) — this will full-scan as data grows. Additionally, since duplicate-name prevention is only enforced at the application layer (check-then-create in space.service.ts), a composite @@unique([ownerId, name]) would both fix the missing index and close the race condition where two concurrent create requests for the same name could both pass the existence check.

♻️ Suggested schema change
 model ResearchSpace {
   id                String   `@id` `@default`(uuid())
   ownerId           String
   owner             User     `@relation`(fields: [ownerId], references: [id])
   name              String
   researchObjective String
   isArchived        Boolean  `@default`(false)
   createdAt         DateTime `@default`(now())
   updatedAt         DateTime `@updatedAt`
   lastOpenedAt      DateTime `@default`(now())

   sources       Source[]
   conversations Conversation[]
   notes         Note[]
   notebook      Notebook?
+
+  @@unique([ownerId, name])
 }
📝 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.

Suggested change
model ResearchSpace {
id String @id @default(uuid())
ownerId String
owner User @relation(fields: [ownerId], references: [id])
name String
researchObjective String
isArchived Boolean @default(false)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
lastOpenedAt DateTime @default(now())
sources Source[]
conversations Conversation[]
notes Note[]
notebook Notebook?
}
enum OriginType {
UserCreated
SavedAssistantAnswer
}
model ResearchSpace {
id String `@id` `@default`(uuid())
ownerId String
owner User `@relation`(fields: [ownerId], references: [id])
name String
researchObjective String
isArchived Boolean `@default`(false)
createdAt DateTime `@default`(now())
updatedAt DateTime `@updatedAt`
lastOpenedAt DateTime `@default`(now())
sources Source[]
conversations Conversation[]
notes Note[]
notebook Notebook?
@@unique([ownerId, name])
}
enum OriginType {
UserCreated
SavedAssistantAnswer
}
🤖 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 `@src/prisma/schema.prisma` around lines 24 - 45, Update the ResearchSpace
model to add a composite unique constraint on ownerId and name, which also
provides the needed lookup index for owner-scoped queries and enforces
duplicate-name prevention at the database level.

Comment on lines +13 to +16
console.log(`Created user: ${user.email} (${user.id})`);
} else {
console.log(`Using existing user: ${user.email} (${user.id})`);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win

Avoid logging user email/PII to console.

Static analysis flags these as sensitive-data logging (CWE-532). Prefer logging the user ID alone, or a masked identifier.

🛡️ Suggested fix
-    console.log(`Created user: ${user.email} (${user.id})`);
+    console.log(`Created user: ${user.id}`);
   } else {
-    console.log(`Using existing user: ${user.email} (${user.id})`);
+    console.log(`Using existing user: ${user.id}`);
   }
📝 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.

Suggested change
console.log(`Created user: ${user.email} (${user.id})`);
} else {
console.log(`Using existing user: ${user.email} (${user.id})`);
}
console.log(`Created user: ${user.id}`);
} else {
console.log(`Using existing user: ${user.id}`);
}
🧰 Tools
🪛 ast-grep (0.45.0)

[warning] 14-14: Avoid logging sensitive data
Context: console.log(Using existing user: ${user.email} (${user.id}))
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 `@src/prisma/seeds/space.seed.ts` around lines 13 - 16, Update the user
creation and reuse log statements around the seed flow to remove user.email from
console output, retaining only the non-sensitive user.id needed for
identification.

Source: Linters/SAST tools

Comment thread tsconfig.json
Comment on lines +3 to +15
"module": "Preserve",
"esModuleInterop": true,
"skipLibCheck": true,
"target": "ES2023",
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"noImplicitAny": true,
"moduleResolution": "NodeNext",
"moduleResolution": "Bundler",
"resolveJsonModule": true,
"baseUrl": ".",
"paths": {
"~/*": ["src/*"]
},

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -eu

node -p "JSON.stringify({type: require('./package.json').type, build: require('./package.json').scripts?.build, start: require('./package.json').scripts?.start}, null, 2)"
rg -n 'from ["'\''](~|\.\.?/)' src

Repository: nustechnology/Folio-BE

Length of output: 7004


🏁 Script executed:

#!/bin/bash
set -eu

printf '\npackage.json:\n'
cat -n package.json | sed -n '1,220p'

printf '\ntsconfig.json:\n'
cat -n tsconfig.json | sed -n '1,220p'

printf '\nRelevant tsconfig/alias files:\n'
rg -n '"module"|"moduleResolution"|"paths"|"outDir"|"rootDir"|"allowImportingTsExtensions"|"rewriteRelativeImportExtensions"|"verbatimModuleSyntax"|"type"' tsconfig*.json package.json

Repository: nustechnology/Folio-BE

Length of output: 3069


🌐 Web query:

tsc-alias default behavior add .js extensions rewrite relative imports path aliases

💡 Result:

By default, tsc-alias does not add.js extensions to import paths; its primary behavior is to replace TypeScript path aliases with valid relative paths in your compiled JavaScript files [1][2]. To add.js (or other) extensions to your imports—a requirement for some ECMAScript Module environments—you must explicitly enable the necessary configuration options [1][3]. Configuration Options: 1. resolveFullPaths: Set this to true to enable the replacement of incomplete import paths with fully resolved paths [1][4]. 2. resolveFullExtension: Use this to specify the desired extension (e.g., '.js', '.mjs', or '.cjs') to be appended to those incomplete paths [1][3]. You can configure these in your tsconfig.json under a dedicated tsc-alias section: { "compilerOptions": { // Standard TypeScript options }, "tsc-alias": { "resolveFullPaths": true, "resolveFullExtension": ".js" } } Alternatively, you can pass these as flags via the command line (e.g., --resolve-full-paths and --resolve-full-extension) [3][5]. When enabled, tsc-alias will attempt to append the specified extension to imports that do not already end in that extension [1][3].

Citations:


🏁 Script executed:

#!/bin/bash
set -eu

printf '\nSearching for tsc-alias configuration:\n'
rg -n '"tsc-alias"|resolveFullPaths|resolveFullExtension|addExtensions|replaceExtensions' . --glob '!node_modules' --glob '!dist'

printf '\nExtensionless relative imports in src:\n'
rg -n 'from\s+[\"\'](\.{1,2}/[^\"\']+)[\"\']' src

printf '\nFiles with only alias imports or bare imports in src (sample):\n'
rg -n 'from\s+[\"\'](\.{1,2}/[^\"\']+)[\"\']|from\s+[\"\']~/' src/api src/config src/prisma src/index.ts

Repository: nustechnology/Folio-BE

Length of output: 338


module: Preserve is only safe here if the runtime uses a loader or path-rewrite step
The package is type: "module", the build emits ESM, and tsc-alias only rewrites path aliases by default — it does not add .js extensions. With the current extensionless relative imports, direct Node execution of dist will fail unless a full-path rewrite or loader is in place.

🤖 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 `@tsconfig.json` around lines 3 - 15, Update the TypeScript
module-resolution/build configuration around module: Preserve so emitted dist
files are directly executable by the package’s Node ESM runtime. Add or
configure the required full-path rewrite step to append .js extensions to
extensionless relative imports, or use an established loader, while preserving
the existing ~/* alias resolution.

Source: MCP tools

@liemhtn
liemhtn marked this pull request as draft July 31, 2026 03:13
@liemhtn
liemhtn force-pushed the feat/mob-29-create-workspace branch 2 times, most recently from 852731a to 88504dd Compare July 31, 2026 03:18
- Updated validation middleware to improve readability.
- Refactored auth router for cleaner import statements and consistent formatting.
- Improved response utility to use generics for better type safety.
- Cleaned up space router imports and formatting.
- Standardized user router imports and formatting.
- Enhanced auth validator schemas for better readability and maintainability.
- Refactored space validator schemas for improved clarity.
- Cleaned up user validator schemas for consistency.
- Refactored auth service for better error handling and code clarity.
- Improved space service for better readability and maintainability.
- Cleaned up user service for consistency and clarity.
- Updated constants for better formatting.
- Refactored token utility for improved readability.
- Cleaned up CORS configuration for consistency.
- Updated environment configuration for better formatting.
- Refactored logger configuration for improved readability.
- Cleaned up index file for better error handling.
- Updated Prisma client configuration for consistency.
- Refactored space repository for improved readability.
- Cleaned up user repository for consistency and clarity.
- Updated space seed data for better formatting.
- Updated yarn.lock for new dependencies and versions.
- Added Prettier configuration for consistent code formatting.
- Created space type definitions for better type safety.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants