@corenotes/server — REST API with email sending, Docker containers, GitHub actions and more#69
@corenotes/server — REST API with email sending, Docker containers, GitHub actions and more#69dmelo-sh wants to merge 1 commit into
@corenotes/server — REST API with email sending, Docker containers, GitHub actions and more#69Conversation
WalkthroughThis change represents a comprehensive migration from an AdonisJS-based TypeScript API to a new server architecture using the Hono framework, Drizzle ORM, and modern tooling. It removes all AdonisJS-specific files, configurations, and tests, and introduces new database schemas, authentication and notes APIs, environment validation, modern build and linting tools, and Docker support. Changes
Sequence Diagram(s)sequenceDiagram
participant Client
participant Server (Hono)
participant DB (Drizzle)
participant Email (Resend)
Client->>Server (POST /auth/links): Request magic link with email
Server->>DB: Store one-time token with expiry
Server->>Email: Send magic link email with token
Email-->>Client: Email with login link
Server-->>Client: 204 No Content
Client->>Server (GET /auth/links/:id): Clicks magic link
Server->>DB: Verify and mark token as used
alt User exists
Server->>DB: Fetch user by email
else New user
Server->>DB: Create user
end
Server->>Client: Set JWT cookie, redirect to website
Client->>Server (DELETE /logout): Logout
Server-->>Client: Clear cookie, 204 No Content
Client->>Server (GET /notes): List notes (auth required)
Server->>DB: Query notes for user
Server-->>Client: Return notes
Client->>Server (POST /notes): Create note
Server->>DB: Insert note
Server-->>Client: Return note ID
Client->>Server (PATCH /notes/:id): Update note
Server->>DB: Update note if owned by user
Server-->>Client: 204 No Content
Client->>Server (DELETE /notes/:id): Delete note
Server->>DB: Delete note if owned by user
Server-->>Client: 204 No Content
Poem
✨ Finishing Touches
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. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Actionable comments posted: 10
🧹 Nitpick comments (12)
.gitignore (1)
1-12: Consider extending the ignore list to cover other common artefactsThe shortened list is clean, but a few high-churn or sensitive items are no longer ignored (e.g. coverage reports, OS cruft, log files, editor swap files, additional
.env.*variants). Accidentally committing any of these can bloat the repo or leak information.+# misc +.DS_Store +*.log + +# test / coverage +coverage + +# editors +*.swp +*.swo + +# all env files +.env.*src/db/connection.ts (1)
1-4: Consider adding connection pooling and error handling for production readiness.The basic connection setup is correct, but for production environments, consider adding connection pooling options and error handling:
import { drizzle } from 'drizzle-orm/node-postgres' +import { Pool } from 'pg' import { config } from '@/config' -export const db = drizzle(config.DATABASE_URL) +const pool = new Pool({ + connectionString: config.DATABASE_URL, + max: 20, + idleTimeoutMillis: 30000, + connectionTimeoutMillis: 2000, +}) + +export const db = drizzle(pool)tsup.config.ts (1)
3-8: Addplatform: 'node'and enable source-maps for a smoother Node build/debug experience
tsupdefaults toplatform: 'neutral'; without explicitly settingplatform: 'node'you may get unnecessary polyfills or different resolution semantics.
In addition, shipping source-maps greatly eases production debugging.entry: ['src/server.ts'], target: 'esnext', outDir: 'dist', format: ['esm'], + platform: 'node', + sourcemap: true,README.md (1)
1-8: Specify a language for fenced code blocks to satisfy markdown-lint & improve readability-``` -npm install -npm run dev -``` +```bash +npm install +npm run dev +``` @@ -``` -open http://localhost:3000 -``` +```bash +open http://localhost:3000 +```.env.example (1)
4-5: Add placeholder values or comments for secrets to prevent empty-string misconfigurationBlank
JWT_SECRET/RESEND_API_KEYoften lead to silent auth/email failures
in staging environments that copy.env.example.
Consider adding descriptive placeholders or comments.JWT_SECRET="replace-with-random-secret" RESEND_API_KEY="your-resend-api-key"Dockerfile (2)
8-9: Optimize Docker layer caching by copying package files first.Copying the entire source code before installing dependencies invalidates Docker's layer cache whenever any file changes. Consider copying only package files first, then installing dependencies, then copying the rest of the source.
-COPY . /app WORKDIR /app +COPY package.json pnpm-lock.yaml ./ +# Install dependencies first for better caching +# Then copy the rest of the source code later
22-22: Consider making the port configurable.The hardcoded port 3333 should ideally match the configurable PORT from your environment configuration, though this may be acceptable for development purposes.
docker-compose.yaml (1)
15-15: Add missing newline at end of file.Static analysis tools flagged the missing newline character at the end of the file.
volumes: postgresql: driver: local +src/middlewares/auth.ts (1)
24-26: Consider more specific error handling.The generic catch block returns the same error message for all JWT verification failures, which is good for security but might make debugging difficult in development.
Consider logging the actual error in development while maintaining generic responses:
} catch (error) { + if (process.env.NODE_ENV === 'development') { + console.error('JWT verification failed:', error) + } return c.json({ error: 'unauthorized' }, 401) }package.json (1)
2-6: Consider adding Node.js version constraints.The package lacks an
enginesfield to specify supported Node.js versions, which could lead to compatibility issues in different environments.Add version constraints:
{ "name": "@corenotes/server", "type": "module", "version": "2.0.2", + "engines": { + "node": ">=18.0.0" + }, "types": "dist/server.d.ts", "main": "dist/server.d.ts",src/routes/notes.ts (1)
98-105: Consider adding color field to creation endpoint.The color field is missing from the request validation schema, forcing all new notes to use the default 'white' color. For consistency with the PATCH endpoint and better user experience, consider allowing color selection during creation.
zValidator( 'json', z.object({ title: z.string(), description: z.string(), + color: z.enum(NOTE_AVAILABLE_COLORS).optional(), favorite: z.boolean(), }), ),And update the insert operation:
await db.insert(_notes).values({ id, user_id: user, title, description, + color, favorite, })src/db/schema.ts (1)
40-42: Consider adding cascade delete for user data cleanup.The foreign key lacks cascade actions. When a user is deleted, their notes will remain in the database causing referential integrity issues.
Consider adding cascade delete if you want automatic cleanup:
user_id: char({ length: 26 }) .notNull() - .references(() => users.id), + .references(() => users.id, { onDelete: 'cascade' }),Alternatively, implement a soft delete pattern or a cleanup service if you need to preserve data for compliance/audit purposes.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (2)
package-lock.jsonis excluded by!**/package-lock.jsonpnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (58)
.adonisrc.json(0 hunks).dockerignore(1 hunks).editorconfig(0 hunks).env.example(1 hunks).github/workflows/publish.yaml(1 hunks).gitignore(1 hunks).gitignore copy(0 hunks).npmrc(1 hunks).prettierignore(0 hunks)Dockerfile(1 hunks)Leiame.md(0 hunks)README.md(1 hunks)ace(0 hunks)ace-manifest.json(0 hunks)app/Controllers/VehiclesController.ts(0 hunks)app/Exceptions/Handler.ts(0 hunks)app/Types/Vehicle.ts(0 hunks)biome.json(1 hunks)commands/index.ts(0 hunks)config/app.ts(0 hunks)config/bodyparser.ts(0 hunks)config/cors.ts(0 hunks)config/drive.ts(0 hunks)config/hash.ts(0 hunks)contracts/drive.ts(0 hunks)contracts/env.ts(0 hunks)contracts/events.ts(0 hunks)contracts/hash.ts(0 hunks)contracts/tests.ts(0 hunks)docker-compose.yaml(1 hunks)drizzle.config.ts(1 hunks)drizzle/0000_tough_epoch.sql(1 hunks)drizzle/0001_huge_lyja.sql(1 hunks)drizzle/meta/0000_snapshot.json(1 hunks)drizzle/meta/0001_snapshot.json(1 hunks)drizzle/meta/_journal.json(1 hunks)env.ts(0 hunks)package.json(1 hunks)providers/AppProvider.ts(0 hunks)server.ts(0 hunks)src/config.ts(1 hunks)src/constants/access-token-cookie-name.ts(1 hunks)src/constants/access-token-lifetime.ts(1 hunks)src/constants/note-available-colors.ts(1 hunks)src/db/connection.ts(1 hunks)src/db/schema.ts(1 hunks)src/lib/resend.ts(1 hunks)src/middlewares/auth.ts(1 hunks)src/routes/auth.ts(1 hunks)src/routes/notes.ts(1 hunks)src/server.ts(1 hunks)start/kernel.ts(0 hunks)start/routes.ts(0 hunks)test.ts(0 hunks)tests/bootstrap.ts(0 hunks)tests/functional/vehicles-api.spec.ts(0 hunks)tsconfig.json(1 hunks)tsup.config.ts(1 hunks)
💤 Files with no reviewable changes (29)
- .prettierignore
- commands/index.ts
- .gitignore copy
- config/hash.ts
- .adonisrc.json
- tests/functional/vehicles-api.spec.ts
- contracts/events.ts
- .editorconfig
- ace-manifest.json
- providers/AppProvider.ts
- contracts/drive.ts
- start/routes.ts
- contracts/tests.ts
- app/Exceptions/Handler.ts
- app/Types/Vehicle.ts
- ace
- app/Controllers/VehiclesController.ts
- contracts/env.ts
- start/kernel.ts
- test.ts
- contracts/hash.ts
- server.ts
- env.ts
- config/bodyparser.ts
- config/cors.ts
- Leiame.md
- config/drive.ts
- config/app.ts
- tests/bootstrap.ts
🧰 Additional context used
🧠 Learnings (2)
.env.example (1)
Learnt from: leandrodomingues2051
PR: corelabbr/corelab-api-challenge#57
File: Dockerfile:1-10
Timestamp: 2025-03-18T22:52:15.229Z
Learning: The Dockerfile in this project is intended for development purposes only, not for production deployment.
Dockerfile (1)
Learnt from: leandrodomingues2051
PR: corelabbr/corelab-api-challenge#57
File: Dockerfile:1-10
Timestamp: 2025-03-18T22:52:15.229Z
Learning: The Dockerfile in this project is intended for development purposes only, not for production deployment.
🧬 Code Graph Analysis (7)
src/db/connection.ts (1)
src/config.ts (1)
config(13-13)
drizzle.config.ts (1)
src/config.ts (1)
config(13-13)
src/lib/resend.ts (1)
src/config.ts (1)
config(13-13)
src/server.ts (5)
src/config.ts (1)
config(13-13)src/middlewares/auth.ts (1)
auth(9-27)src/routes/auth.ts (1)
auth(129-129)src/db/schema.ts (1)
notes(38-47)src/routes/notes.ts (1)
notes(217-217)
src/middlewares/auth.ts (3)
src/routes/auth.ts (1)
auth(129-129)src/constants/access-token-cookie-name.ts (1)
ACCESS_TOKEN_COOKIE_NAME(1-1)src/config.ts (1)
config(13-13)
src/routes/notes.ts (5)
src/db/schema.ts (2)
notes(38-47)color(22-36)src/middlewares/auth.ts (2)
MiddlewareVariables(7-7)auth(9-27)src/routes/auth.ts (1)
auth(129-129)src/constants/note-available-colors.ts (1)
NOTE_AVAILABLE_COLORS(1-15)src/db/connection.ts (1)
db(4-4)
src/routes/auth.ts (7)
src/middlewares/auth.ts (1)
auth(9-27)src/db/connection.ts (1)
db(4-4)src/db/schema.ts (2)
one_time_tokens(15-20)users(10-13)src/config.ts (1)
config(13-13)src/lib/resend.ts (1)
resend(4-4)src/constants/access-token-lifetime.ts (1)
ACCESS_TOKEN_LIFETIME(1-1)src/constants/access-token-cookie-name.ts (1)
ACCESS_TOKEN_COOKIE_NAME(1-1)
🪛 markdownlint-cli2 (0.17.2)
README.md
1-1: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
6-6: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
🪛 YAMLlint (1.37.1)
docker-compose.yaml
[error] 15-15: no new line character at the end of file
(new-line-at-end-of-file)
🔇 Additional comments (26)
src/constants/access-token-lifetime.ts (1)
1-1: LGTM – constant is clear and self-explanatorysrc/constants/access-token-cookie-name.ts (1)
1-1: LGTM – consistent, centralised cookie name.npmrc (1)
1-2: Double-check that the token is only injected in CI and never committedThe file is fine as is, but ensure:
NODE_AUTH_TOKENis provided solely by the CI secret store..npmrcis excluded from runtime images (see .dockerignore comment).No action required if both are guaranteed.
drizzle/meta/_journal.json (1)
8-16: Verify the migration timestamps.The timestamps in the migration entries appear to be from the future (January 2025), which may indicate system clock issues during migration generation:
- Entry 0:
1751374298374→ ~Jan 1, 2025- Entry 1:
1751408438858→ ~Jan 1, 2025Please verify that these migrations were generated with the correct system time to avoid potential issues with migration ordering.
src/lib/resend.ts (1)
1-4: LGTM! Clean and simple Resend client setup.The implementation correctly uses the validated config and follows good patterns for service client initialization. The client will be properly typed and ready for use throughout the application.
drizzle/0001_huge_lyja.sql (1)
1-4: LGTM! Proper table rename with foreign key constraint handling.The migration correctly handles the table rename while maintaining referential integrity by properly dropping and recreating the foreign key constraint with the updated table name.
src/constants/note-available-colors.ts (1)
1-15: Colors match database enumVerified that all entries in
NOTE_AVAILABLE_COLORSperfectly align with thecolorenum defined in drizzle/0000_tough_epoch.sql. No mismatches found—approving as-is.drizzle.config.ts (1)
8-10: Ensure env vars are loaded before accessingconfig.DATABASE_URLWhen you run
pnpm drizzle-kit generate, no.envis loaded by default, so
DATABASE_URLmay beundefined, causing a runtime error here. Load dotenv at
the top or rely onprocess.env.DATABASE_URLdirectly.import 'dotenv/config' // <-- add before other imports.github/workflows/publish.yaml (1)
13-13: Node 22 is not LTS and may be unavailable on runnersConsider pinning to the latest LTS (
20.x) unless you have a specific 22-only
requirement. This avoids unexpected CI breakage when runners lag behind
up-stream releases.src/config.ts (1)
1-13: Excellent environment configuration setup with Zod validation.The implementation follows best practices by:
- Using runtime validation to fail fast on invalid environment variables
- Properly validating URLs, secrets, and enum values
- Providing sensible defaults where appropriate
- Ensuring type safety through Zod parsing
src/server.ts (4)
10-19: CORS configuration looks secure and appropriate.The CORS setup properly restricts origins to the configured website URL, allows necessary HTTP methods, and enables credentials for authentication flows.
23-37: Well-implemented API documentation endpoints.The setup provides both human-readable documentation via Scalar and machine-readable OpenAPI specs, which is excellent for API discoverability and integration.
39-50: Clean server startup and type export.The server configuration and AppType export follow best practices for modern Node.js applications.
20-21: Double-check for potential route conflictsBoth
authandnotesrouters are mounted at the root path (/). Verify that no HTTP method + path combinations overlap between the two modules, which would cause one set of routes to shadow the other.Inspect the endpoint definitions in:
src/routes/auth.tssrc/routes/notes.tsand confirm that each method+path pair is unique across both files.
biome.json (1)
1-41: Well-configured Biome setup for code quality.The configuration follows best practices with:
- Appropriate file inclusion/exclusion patterns
- Consistent formatting rules (2-space indentation, 80-char line width)
- Recommended linting rules with targeted customizations
- JavaScript-specific formatting preferences
This effectively replaces ESLint/Prettier with a unified tool.
docker-compose.yaml (1)
1-15: PostgreSQL service configuration looks good for development.The setup provides a properly configured PostgreSQL instance with:
- Appropriate image choice (bitnami/postgresql)
- Standard port mapping
- Persistent volume for data
- Clear environment variable configuration
src/middlewares/auth.ts (1)
9-27: Well-structured authentication middleware.The middleware correctly implements JWT authentication with both header and cookie support, which provides flexibility for different client types. The error handling maintains security by returning consistent 401 responses.
tsconfig.json (1)
1-25: Modern TypeScript configuration aligns well with the new stack.The configuration changes properly support the migration from AdonisJS to Hono:
- ESNext target with module preservation is ideal for modern bundler workflows
- JSX configuration with "hono/jsx" import source enables server-side rendering capabilities
- Simplified path alias structure reduces complexity
- Output directory change to "dist" is consistent with the new build setup
package.json (1)
19-39: Comprehensive dependency migration executed well.The transition from AdonisJS to the modern Hono/Drizzle/Biome stack is well-executed:
- All dependencies are current and appropriate for the new architecture
- Development tooling (tsx, tsup, biome) provides excellent developer experience
- Database tooling (drizzle-orm, drizzle-kit) offers type-safe database operations
- API tooling (@scalar/hono-api-reference, hono-openapi) enables proper documentation
drizzle/meta/0000_snapshot.json (1)
1-170: Well-designed initial database schema.The schema demonstrates solid database design principles:
- ULID usage (char(26)) for primary keys provides better distributed system support than UUIDs
- One-time tokens table properly supports passwordless authentication flows
- Foreign key constraints ensure referential integrity
- Color enum provides controlled vocabulary for UI consistency
- Proper indexing strategy with primary keys and unique constraints
Note: This snapshot shows the initial schema with the
taskstable, which is later renamed tonotesin the subsequent migration.drizzle/meta/0001_snapshot.json (2)
7-67: Table rename properly implemented in schema evolution.The migration from
taskstonotestable is handled correctly:
- Foreign key constraint updated from
tasks_user_id_users_id_fktonotes_user_id_users_id_fk- All column definitions and constraints preserved
- Schema versioning properly tracked with updated prevId reference
This naming change better aligns with the application domain and the
@corenotes/serverpackage name.
139-158: Color enum provides comprehensive palette options.The color enum offers a well-balanced set of 13 color options that should cover most user preferences for note categorization. The naming convention (e.g.,
light_blue,mint_green) is clear and descriptive.src/routes/notes.ts (1)
15-80: Well-implemented list endpoint with proper user isolation.The endpoint correctly filters notes by the authenticated user and supports flexible query parameters. The case-insensitive search on title and ordering by ID (which uses ULID) provides a good user experience.
src/routes/auth.ts (1)
77-84: Excellent token validation and consumption logic.The implementation properly validates the token existence, expiration, and usage status, then immediately marks it as used to prevent replay attacks.
src/db/schema.ts (1)
1-47: Well-structured database schema.The schema is clean and follows best practices with proper constraints, defaults, and type safety using Drizzle ORM.
drizzle/0000_tough_epoch.sql (1)
1-24: Migration correctly implements the initial schema.The migration properly creates all required tables and constraints. The 'tasks' table naming will be corrected to 'notes' in the subsequent migration file.
| # dependencies | ||
| node_modules | ||
|
|
||
| # build | ||
| dist | ||
|
|
||
| # git | ||
| .git | ||
| .gitignore | ||
|
|
||
| # docs | ||
| *.md No newline at end of file |
There was a problem hiding this comment.
Add .npmrc (and optionally all .env* files) to prevent secrets leaking into the Docker image
The Docker build context currently includes .npmrc, which contains an auth-token placeholder. If CI injects a real token, it will be copied into every layer and can be extracted from the final image.
# docs
*.md
+
+# npm credentials / env
+.npmrc
+.env*📝 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.
| # dependencies | |
| node_modules | |
| # build | |
| dist | |
| # git | |
| .git | |
| .gitignore | |
| # docs | |
| *.md | |
| # dependencies | |
| node_modules | |
| # build | |
| dist | |
| # git | |
| .git | |
| .gitignore | |
| # docs | |
| *.md | |
| # npm credentials / env | |
| .npmrc | |
| .env* |
🤖 Prompt for AI Agents
In the .dockerignore file from lines 1 to 12, add `.npmrc` and optionally all
`.env*` files to the ignore list to prevent sensitive information like auth
tokens and environment variables from being included in the Docker build context
and image layers. This will help avoid leaking secrets into the final Docker
image.
| import { defineConfig } from 'drizzle-kit' | ||
| import { config } from '@/config' | ||
|
|
There was a problem hiding this comment.
Path alias @/config will break when Drizzle CLI executes this file
drizzle-kit loads this config with the plain Node resolver – TS/ESM path
aliases are not honoured. Unless you register tsconfig-paths or similar in
the CLI invocation, this import will throw MODULE_NOT_FOUND.
-import { config } from '@/config'
+import { config } from './src/config'📝 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.
| import { defineConfig } from 'drizzle-kit' | |
| import { config } from '@/config' | |
| import { defineConfig } from 'drizzle-kit' | |
| import { config } from './src/config' |
🤖 Prompt for AI Agents
In drizzle.config.ts at lines 1 to 3, the import using the path alias '@/config'
will fail when Drizzle CLI runs because it does not resolve TS/ESM path aliases.
Replace the alias import with a relative or absolute path that Node.js can
resolve without additional configuration, ensuring the import works correctly
during CLI execution.
| - name: Install dependencoies and build | ||
| run: | | ||
| pnpm install --frozen-lockfile | ||
| pnpm build --dts-only | ||
|
|
There was a problem hiding this comment.
Typo in step name and publishing only d.ts will ship an empty package
dependencoies→dependencies.pnpm build --dts-onlyproduces declarations but no JS, so consumers
will get a broken install. Run the full build instead:
- pnpm install --frozen-lockfile
- pnpm build --dts-only
+ pnpm install --frozen-lockfile
+ pnpm run build # generates JS + d.ts📝 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.
| - name: Install dependencoies and build | |
| run: | | |
| pnpm install --frozen-lockfile | |
| pnpm build --dts-only | |
| - name: Install dependencoies and build | |
| run: | | |
| pnpm install --frozen-lockfile | |
| pnpm run build # generates JS + d.ts |
🤖 Prompt for AI Agents
In .github/workflows/publish.yaml around lines 29 to 33, fix the typo in the
step name by changing "dependencoies" to "dependencies". Also, replace the
command "pnpm build --dts-only" with a full build command that generates both
JavaScript and declaration files to avoid shipping an empty package.
| FROM base | ||
| COPY --from=deps /app/node_modules /app/node_modules | ||
| COPY --from=build /app/dist /app/dist | ||
|
|
||
| EXPOSE 3333 | ||
| CMD [ "pnpm", "start" ] No newline at end of file |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Add user security and fix working directory.
The final stage should set a working directory and avoid running as root for security.
FROM base
+WORKDIR /app
+RUN addgroup -g 1001 -S nodejs && adduser -S nodejs -u 1001
COPY --from=deps /app/node_modules /app/node_modules
COPY --from=build /app/dist /app/dist
+USER nodejs
EXPOSE 3333
CMD [ "pnpm", "start" ]📝 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.
| FROM base | |
| COPY --from=deps /app/node_modules /app/node_modules | |
| COPY --from=build /app/dist /app/dist | |
| EXPOSE 3333 | |
| CMD [ "pnpm", "start" ] | |
| FROM base | |
| WORKDIR /app | |
| RUN addgroup -g 1001 -S nodejs && adduser -S nodejs -u 1001 | |
| COPY --from=deps /app/node_modules /app/node_modules | |
| COPY --from=build /app/dist /app/dist | |
| USER nodejs | |
| EXPOSE 3333 | |
| CMD [ "pnpm", "start" ] |
🤖 Prompt for AI Agents
In Dockerfile lines 18 to 23, the final stage lacks a working directory and runs
as root, which is a security risk. Add a WORKDIR instruction to set the working
directory to /app, and add a USER instruction to switch to a non-root user
before the CMD line. Ensure the user exists or create one if necessary to avoid
running the container as root.
| try { | ||
| const decoded = await verify(token, config.JWT_SECRET) | ||
| const sub = decoded.sub as string | ||
|
|
There was a problem hiding this comment.
🛠️ Refactor suggestion
Improve type safety for JWT payload validation.
The type assertion decoded.sub as string assumes the sub claim exists and is a string without validation. This could lead to runtime errors if the JWT payload structure is unexpected.
Consider adding proper validation:
try {
const decoded = await verify(token, config.JWT_SECRET)
- const sub = decoded.sub as string
+
+ if (!decoded.sub || typeof decoded.sub !== 'string') {
+ return c.json({ error: 'unauthorized' }, 401)
+ }
+
+ const sub = decoded.sub
c.set('user', sub)
await next()📝 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.
| try { | |
| const decoded = await verify(token, config.JWT_SECRET) | |
| const sub = decoded.sub as string | |
| try { | |
| const decoded = await verify(token, config.JWT_SECRET) | |
| if (!decoded.sub || typeof decoded.sub !== 'string') { | |
| return c.json({ error: 'unauthorized' }, 401) | |
| } | |
| const sub = decoded.sub | |
| c.set('user', sub) | |
| await next() |
🤖 Prompt for AI Agents
In src/middlewares/auth.ts around lines 18 to 21, the code uses a type assertion
for decoded.sub without validating its existence or type, which risks runtime
errors. To fix this, add explicit checks to confirm that decoded.sub exists and
is a string before using it. If the validation fails, handle the error
appropriately, such as throwing an error or returning an unauthorized response,
to ensure type safety and robustness.
| "scripts": { | ||
| "dev": "tsx watch src/server.ts", | ||
| "build": "tsup", | ||
| "start": "node dist/server.js", | ||
| "lint": "biome check", | ||
| "lint:fix": "biome check --write", | ||
| "db:generate": "drizzle-kit generate", | ||
| "db:migrate": "drizzle-kit migrate" | ||
| }, |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Add test infrastructure to the scripts.
The migration removed all test-related scripts and dependencies. Consider adding test infrastructure for the new codebase to ensure reliability.
Consider adding test scripts and dependencies:
"scripts": {
"dev": "tsx watch src/server.ts",
"build": "tsup",
"start": "node dist/server.js",
+ "test": "vitest",
+ "test:watch": "vitest --watch",
"lint": "biome check",
"lint:fix": "biome check --write",
"db:generate": "drizzle-kit generate",
"db:migrate": "drizzle-kit migrate"
},And add test dependencies:
"devDependencies": {
"@types/node": "^20.11.17",
"drizzle-kit": "^0.31.4",
"tsup": "^8.5.0",
"tsx": "^4.7.1",
- "typescript": "^5.8.3"
+ "typescript": "^5.8.3",
+ "vitest": "^1.0.0"
}📝 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.
| "scripts": { | |
| "dev": "tsx watch src/server.ts", | |
| "build": "tsup", | |
| "start": "node dist/server.js", | |
| "lint": "biome check", | |
| "lint:fix": "biome check --write", | |
| "db:generate": "drizzle-kit generate", | |
| "db:migrate": "drizzle-kit migrate" | |
| }, | |
| "scripts": { | |
| "dev": "tsx watch src/server.ts", | |
| "build": "tsup", | |
| "start": "node dist/server.js", | |
| "test": "vitest", | |
| "test:watch": "vitest --watch", | |
| "lint": "biome check", | |
| "lint:fix": "biome check --write", | |
| "db:generate": "drizzle-kit generate", | |
| "db:migrate": "drizzle-kit migrate" | |
| }, | |
| "devDependencies": { | |
| "@types/node": "^20.11.17", | |
| "drizzle-kit": "^0.31.4", | |
| "tsup": "^8.5.0", | |
| "tsx": "^4.7.1", | |
| "typescript": "^5.8.3", | |
| "vitest": "^1.0.0" | |
| } |
🤖 Prompt for AI Agents
In package.json around lines 10 to 18, the test-related scripts and dependencies
were removed, leaving no test infrastructure. Add test scripts such as "test"
and "test:watch" to the scripts section, using a test runner like Jest or
Vitest. Also, add the corresponding test dependencies to the devDependencies
section to enable running and maintaining tests for the codebase.
| return c.json({ error: 'note_not_found' }, 404) | ||
| } | ||
|
|
||
| await db.delete(_notes).where(eq(_notes.id, id)) |
There was a problem hiding this comment.
Add user_id check to the delete query for defense in depth.
Similar to the PATCH endpoint, add user_id to the WHERE clause for additional security.
- await db.delete(_notes).where(eq(_notes.id, id))
+ await db.delete(_notes).where(and(eq(_notes.id, id), eq(_notes.user_id, user)))📝 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 db.delete(_notes).where(eq(_notes.id, id)) | |
| await db.delete(_notes) | |
| .where(and( | |
| eq(_notes.id, id), | |
| eq(_notes.user_id, user), | |
| )) |
🤖 Prompt for AI Agents
In src/routes/notes.ts at line 211, the delete query currently only filters by
note id. To enhance security, modify the WHERE clause to also check that the
user_id matches the current user's id, similar to the PATCH endpoint. This adds
an additional layer of defense by ensuring users can only delete their own
notes.
| await db | ||
| .update(_notes) | ||
| .set({ title, description, color, favorite }) | ||
| .where(eq(_notes.id, id)) | ||
|
|
There was a problem hiding this comment.
Add user_id check to the update query for defense in depth.
While you verify note ownership before updating, the actual update query doesn't include the user_id check. This could theoretically lead to race conditions.
await db
.update(_notes)
.set({ title, description, color, favorite })
- .where(eq(_notes.id, id))
+ .where(and(eq(_notes.id, id), eq(_notes.user_id, user)))📝 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 db | |
| .update(_notes) | |
| .set({ title, description, color, favorite }) | |
| .where(eq(_notes.id, id)) | |
| await db | |
| .update(_notes) | |
| .set({ title, description, color, favorite }) | |
| .where(and(eq(_notes.id, id), eq(_notes.user_id, user))) |
🤖 Prompt for AI Agents
In src/routes/notes.ts around lines 169 to 173, the update query lacks a user_id
condition, which risks race conditions despite prior ownership checks. Modify
the update query to include a where clause that checks both the note id and the
user_id to ensure only the owner can update the note. This adds defense in depth
by enforcing ownership at the database query level.
| setCookie(c, ACCESS_TOKEN_COOKIE_NAME, accessToken, { | ||
| httpOnly: true, | ||
| maxAge: ACCESS_TOKEN_LIFETIME, | ||
| }) |
There was a problem hiding this comment.
Add secure cookie settings for production.
The cookie is missing important security attributes for production use.
setCookie(c, ACCESS_TOKEN_COOKIE_NAME, accessToken, {
httpOnly: true,
maxAge: ACCESS_TOKEN_LIFETIME,
+ secure: config.NODE_ENV === 'production',
+ sameSite: 'lax',
})📝 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.
| setCookie(c, ACCESS_TOKEN_COOKIE_NAME, accessToken, { | |
| httpOnly: true, | |
| maxAge: ACCESS_TOKEN_LIFETIME, | |
| }) | |
| setCookie(c, ACCESS_TOKEN_COOKIE_NAME, accessToken, { | |
| httpOnly: true, | |
| maxAge: ACCESS_TOKEN_LIFETIME, | |
| secure: config.NODE_ENV === 'production', | |
| sameSite: 'lax', | |
| }) |
🤖 Prompt for AI Agents
In src/routes/auth.ts around lines 105 to 108, the setCookie call for the access
token cookie lacks secure attributes needed for production. Update the cookie
options to include 'secure: true' and 'sameSite: "strict"' when in a production
environment to enhance security. Ensure these attributes are conditionally
applied based on the environment to avoid issues in development.
| await resend.emails.send({ | ||
| from: 'Corenotes <corenotes@dcdms.me>', | ||
| to: [email], | ||
| subject: 'Login to Corenotes', | ||
| html: 'Your link is ' + link.href, | ||
| }) |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Make sender email configurable and improve email template.
The sender email is hardcoded and the email template is minimal. Consider making these configurable for production use.
await resend.emails.send({
- from: 'Corenotes <corenotes@dcdms.me>',
+ from: config.EMAIL_FROM || 'Corenotes <noreply@example.com>',
to: [email],
subject: 'Login to Corenotes',
- html: 'Your link is ' + link.href,
+ html: `
+ <div style="font-family: sans-serif; max-width: 600px; margin: 0 auto;">
+ <h2>Login to Corenotes</h2>
+ <p>Click the link below to log in to your account:</p>
+ <p><a href="${link.href}" style="display: inline-block; padding: 12px 24px; background-color: #0066cc; color: white; text-decoration: none; border-radius: 4px;">Log in to Corenotes</a></p>
+ <p style="color: #666; font-size: 14px;">This link will expire in 5 minutes.</p>
+ <p style="color: #666; font-size: 14px;">If you didn't request this email, you can safely ignore it.</p>
+ </div>
+ `,
})📝 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 resend.emails.send({ | |
| from: 'Corenotes <corenotes@dcdms.me>', | |
| to: [email], | |
| subject: 'Login to Corenotes', | |
| html: 'Your link is ' + link.href, | |
| }) | |
| await resend.emails.send({ | |
| from: config.EMAIL_FROM || 'Corenotes <noreply@example.com>', | |
| to: [email], | |
| subject: 'Login to Corenotes', | |
| html: ` | |
| <div style="font-family: sans-serif; max-width: 600px; margin: 0 auto;"> | |
| <h2>Login to Corenotes</h2> | |
| <p>Click the link below to log in to your account:</p> | |
| <p><a href="${link.href}" style="display: inline-block; padding: 12px 24px; background-color: #0066cc; color: white; text-decoration: none; border-radius: 4px;">Log in to Corenotes</a></p> | |
| <p style="color: #666; font-size: 14px;">This link will expire in 5 minutes.</p> | |
| <p style="color: #666; font-size: 14px;">If you didn't request this email, you can safely ignore it.</p> | |
| </div> | |
| `, | |
| }) |
🤖 Prompt for AI Agents
In src/routes/auth.ts around lines 47 to 52, the sender email is hardcoded and
the email template is very basic. Refactor the code to read the sender email
address from a configuration or environment variable instead of hardcoding it.
Also, improve the email content by creating a more complete and user-friendly
HTML template, possibly using a template literal or an external template file,
to enhance the email's appearance and clarity.
@corenotes/server — REST API with email sending, Docker containers, GitHub actions and more
@corenotes/server
The REST API that powers the corenotes website, providing passwordless authentication and notes management routes. You can see the working application following the links:
Summary by CodeRabbit
New Features
Chores
Bug Fixes
Documentation
Refactor