Skip to content

@corenotes/server — REST API with email sending, Docker containers, GitHub actions and more#69

Open
dmelo-sh wants to merge 1 commit into
corelabbr:mainfrom
dmelo-sh:main
Open

@corenotes/server — REST API with email sending, Docker containers, GitHub actions and more#69
dmelo-sh wants to merge 1 commit into
corelabbr:mainfrom
dmelo-sh:main

Conversation

@dmelo-sh

@dmelo-sh dmelo-sh commented Jul 2, 2025

Copy link
Copy Markdown

@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

    • Introduced a new authentication system with magic link login, logout, and JWT-based session management.
    • Added a complete notes API with endpoints to create, list, update, delete, and filter notes by color, favorite status, and search text.
    • Implemented OpenAPI documentation and a reference UI for the API.
    • Added database schema and migrations for users, notes, and one-time tokens, including color support for notes.
  • Chores

    • Migrated the project from AdonisJS to a new stack using Hono, Drizzle ORM, and Biome.
    • Added Docker and Docker Compose support for development and deployment.
    • Updated environment variable validation and configuration management.
    • Simplified and modernized project scripts, dependencies, and configuration files.
  • Bug Fixes

    • Improved environment variable validation and error handling for authentication and note management.
  • Documentation

    • Replaced the detailed README with concise setup and usage instructions.
    • Added OpenAPI and Scalar documentation endpoints.
  • Refactor

    • Removed all legacy AdonisJS files, configurations, and tests to streamline the codebase for the new architecture.

@coderabbitai

coderabbitai Bot commented Jul 2, 2025

Copy link
Copy Markdown

Walkthrough

This 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

Files / Groups Change Summary
.adonisrc.json, .editorconfig, .prettierignore, .gitignore copy, ace, ace-manifest.json, app/Controllers/VehiclesController.ts, app/Exceptions/Handler.ts, app/Types/Vehicle.ts, commands/index.ts, config/app.ts, config/bodyparser.ts, config/cors.ts, config/drive.ts, config/hash.ts, contracts/drive.ts, contracts/env.ts, contracts/events.ts, contracts/hash.ts, contracts/tests.ts, env.ts, providers/AppProvider.ts, server.ts, start/kernel.ts, start/routes.ts, test.ts, tests/bootstrap.ts, tests/functional/vehicles-api.spec.ts All AdonisJS-related code, configuration, and tests deleted.
.gitignore Simplified to ignore only node_modules, dist, and .env files.
.dockerignore, Dockerfile, docker-compose.yaml Added Docker and Docker Compose setup for development and production, including PostgreSQL service.
.env.example Replaced AdonisJS environment variables with new variables for URLs, database, secrets, and port.
README.md Replaced detailed challenge instructions with minimal usage commands.
Leiame.md Deleted challenge documentation.
package.json Replaced AdonisJS dependencies, scripts, and config with Hono, Drizzle, Biome, and modern tooling.
tsconfig.json Migrated from AdonisJS preset to generic, strict TypeScript config with bundler and JSX support.
biome.json Added Biome configuration for linting and formatting.
.npmrc Added npm registry and authentication token configuration.
drizzle.config.ts, tsup.config.ts Added Drizzle ORM and TSUP build tool configuration files.
drizzle/0000_tough_epoch.sql, drizzle/0001_huge_lyja.sql, drizzle/meta/0000_snapshot.json, drizzle/meta/0001_snapshot.json, drizzle/meta/_journal.json Added initial and updated database schema migrations and snapshots for notes, users, tokens, and color enum.
src/config.ts Added Zod-based environment variable validation and export of validated config.
src/constants/access-token-cookie-name.ts, src/constants/access-token-lifetime.ts, src/constants/note-available-colors.ts Added constants for cookie name, token lifetime, and allowed note colors.
src/db/connection.ts Added Drizzle ORM database connection setup.
src/db/schema.ts Added Drizzle ORM schema for users, notes, tokens, and color enum.
src/lib/resend.ts Added Resend email client setup using app config.
src/middlewares/auth.ts Added JWT authentication middleware for Hono with context typing.
src/routes/auth.ts Added Hono routes for magic link authentication and logout.
src/routes/notes.ts Added Hono routes for CRUD operations on notes, with filtering and user isolation.
src/server.ts Added Hono server setup, global CORS, OpenAPI docs, and route mounting.
.github/workflows/publish.yaml Added GitHub Actions workflow for publishing to NPM.

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
Loading

Poem

🐇
The garden’s changed, the burrow’s new,
Old tools and trails bid swift adieu.
With Hono’s breeze and Drizzle’s rain,
Fresh notes and magic links remain.
Docker ships and Biome prunes,
This rabbit hums new server tunes!
🌱✨

✨ Finishing Touches
  • 📝 Generate Docstrings

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.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need 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)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@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: 10

🧹 Nitpick comments (12)
.gitignore (1)

1-12: Consider extending the ignore list to cover other common artefacts

The 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: Add platform: 'node' and enable source-maps for a smoother Node build/debug experience

tsup defaults to platform: 'neutral'; without explicitly setting platform: '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 misconfiguration

Blank JWT_SECRET / RESEND_API_KEY often 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 engines field 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

📥 Commits

Reviewing files that changed from the base of the PR and between 328ccdd and 060a4aa.

⛔ Files ignored due to path filters (2)
  • package-lock.json is excluded by !**/package-lock.json
  • pnpm-lock.yaml is 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-explanatory

src/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 committed

The file is fine as is, but ensure:

  1. NODE_AUTH_TOKEN is provided solely by the CI secret store.
  2. .npmrc is 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, 2025

Please 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 enum

Verified that all entries in NOTE_AVAILABLE_COLORS perfectly align with the color enum 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 accessing config.DATABASE_URL

When you run pnpm drizzle-kit generate, no .env is loaded by default, so
DATABASE_URL may be undefined, causing a runtime error here. Load dotenv at
the top or rely on process.env.DATABASE_URL directly.

import 'dotenv/config'          // <-- add before other imports
.github/workflows/publish.yaml (1)

13-13: Node 22 is not LTS and may be unavailable on runners

Consider 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 conflicts

Both auth and notes routers 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.ts
  • src/routes/notes.ts

and 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 tasks table, which is later renamed to notes in the subsequent migration.

drizzle/meta/0001_snapshot.json (2)

7-67: Table rename properly implemented in schema evolution.

The migration from tasks to notes table is handled correctly:

  • Foreign key constraint updated from tasks_user_id_users_id_fk to notes_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/server package 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.

Comment thread .dockerignore
Comment on lines +1 to +12
# dependencies
node_modules

# build
dist

# git
.git
.gitignore

# docs
*.md No newline at end of file

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue

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.

Suggested change
# 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.

Comment thread drizzle.config.ts
Comment on lines +1 to +3
import { defineConfig } from 'drizzle-kit'
import { config } from '@/config'

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue

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.

Suggested change
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.

Comment on lines +29 to +33
- name: Install dependencoies and build
run: |
pnpm install --frozen-lockfile
pnpm build --dts-only

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue

Typo in step name and publishing only d.ts will ship an empty package

  1. dependencoiesdependencies.
  2. pnpm build --dts-only produces 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.

Suggested change
- 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.

Comment thread Dockerfile
Comment on lines +18 to +23
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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ 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.

Suggested change
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.

Comment thread src/middlewares/auth.ts
Comment on lines +18 to +21
try {
const decoded = await verify(token, config.JWT_SECRET)
const sub = decoded.sub as string

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ 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.

Suggested change
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.

Comment thread package.json
Comment on lines +10 to 18
"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"
},

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ 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.

Suggested change
"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.

Comment thread src/routes/notes.ts
return c.json({ error: 'note_not_found' }, 404)
}

await db.delete(_notes).where(eq(_notes.id, 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.

⚠️ Potential issue

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.

Suggested change
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.

Comment thread src/routes/notes.ts
Comment on lines +169 to +173
await db
.update(_notes)
.set({ title, description, color, favorite })
.where(eq(_notes.id, 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.

⚠️ Potential issue

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.

Suggested change
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.

Comment thread src/routes/auth.ts
Comment on lines +105 to +108
setCookie(c, ACCESS_TOKEN_COOKIE_NAME, accessToken, {
httpOnly: true,
maxAge: ACCESS_TOKEN_LIFETIME,
})

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue

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.

Suggested change
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.

Comment thread src/routes/auth.ts
Comment on lines +47 to +52
await resend.emails.send({
from: 'Corenotes <corenotes@dcdms.me>',
to: [email],
subject: 'Login to Corenotes',
html: 'Your link is ' + link.href,
})

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ 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.

Suggested change
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.

@dmelo-sh dmelo-sh changed the title Simple REST API with Docker + CI/CD + RPC Integration @corenotes/server — REST API with email sending, Docker containers, GitHub actions and more Jul 2, 2025
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.

1 participant