Skip to content

Init mono#43

Open
husamql3 wants to merge 2 commits intomainfrom
init-mono
Open

Init mono#43
husamql3 wants to merge 2 commits intomainfrom
init-mono

Conversation

@husamql3
Copy link
Copy Markdown
Owner

@husamql3 husamql3 commented Apr 7, 2026

Summary by CodeRabbit

Release Notes

  • New Features

    • Backend server with content collection management APIs
    • Multi-method authentication supporting social login (Google, GitHub, Twitter) and email verification
    • Automatic API documentation generation
    • Request logging and health monitoring
  • Chores

    • Monorepo workspace setup with Turbo orchestration
    • PostgreSQL database configuration
    • Production deployment configuration

husamql3 and others added 2 commits April 7, 2026 21:13
- Add turbo.json and configure Turborepo pipeline
- Add apps/server as a git submodule
- Update package.json for monorepo workspace setup
- Update .gitignore to exclude .turbo
- Comment out stale oxfmt stylesheet config
- Clean up route files and remove old db schema
- Add server/.gitignore and empty TODO.md

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@vercel
Copy link
Copy Markdown

vercel bot commented Apr 7, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
keeply Error Error Apr 7, 2026 6:20pm
keeply-serverr Ready Ready Preview, Comment Apr 7, 2026 6:20pm

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 7, 2026

📝 Walkthrough

Walkthrough

This PR establishes a new Bun-based backend server package for the monorepo using Elysia. It introduces database infrastructure with PostgreSQL and Drizzle ORM, environment validation, authentication via better-auth with email OTP, email capabilities via Nodemailer, and a RESTful collections API with auth middleware. Additionally, it restructures the root package.json for monorepo management with Turbo and updates Tailwind class ordering in several frontend components.

Changes

Cohort / File(s) Summary
Root Monorepo Configuration
.gitignore, .oxfmtrc.json, package.json, turbo.json
Updated .gitignore to exclude .turbo; modified formatter config to comment out Tailwind stylesheet and sort-imports side-effect group; restructured package.json to define workspaces under apps/*, add Turbo-based scripts, and adjust dependencies; added turbo.json with task definitions for build, lint, dev, and database operations.
Server Project Setup
apps/server/.gitignore, apps/server/README.md, apps/server/package.json, apps/server/tsconfig.json, apps/server/tsdown.config.ts, apps/server/vercel.json
Created complete server project scaffolding including Node.js/Bun ignore rules, getting-started documentation, package manifest (Elysia, Supabase, better-auth, Drizzle, Nodemailer dependencies), TypeScript configuration, build tool config, and Vercel deployment settings.
Server Environment & Database
apps/server/src/env.ts, apps/server/src/db/index.ts, apps/server/src/db/schema.ts
Added environment variable validation using @t3-oss/env-core and Zod for database, auth, OAuth, email, and Supabase credentials; initialized Drizzle ORM client with PostgreSQL; updated database schema to use UUID primary keys with defaultRandom() and normalized column definitions.
Server Core & Middleware
apps/server/src/index.ts, apps/server/src/middlewares/logger.ts, apps/server/src/middlewares/auth.ts
Created main Elysia app under /api prefix with OpenAPI documentation, CORS, health check endpoint, request/response logging middleware with color-coded status and timing, and authentication middleware for session retrieval.
Server Authentication & Email
apps/server/src/lib/auth/index.ts, apps/server/src/lib/email/index.ts, apps/server/src/lib/email/transporter.ts, apps/server/src/lib/supabase.ts
Configured better-auth instance with Google/GitHub/Twitter OAuth, Drizzle persistence, email OTP plugin with Nodemailer integration, and OpenAPI schema generation; set up Gmail SMTP transporter and Supabase client initialization.
Collections REST API
apps/server/src/routes/collection/{index,dao,service,model,utils}.ts
Implemented full REST API for collections (GET /, POST /, GET /:id, PUT /:id, DELETE /:id) with ownership validation and private collection access control; added data access layer (DAO), business logic service, request/response models with Zod/Drizzle schemas, and slug generation utilities.
Server Type Definitions
apps/server/src/types/base-response.type.ts, apps/server/src/types/email.types.ts
Defined generic BaseResponse<T> union type for API responses and SendEmailProps interface for email parameters.
Frontend Styling Updates
src/components/DefaultCatchBoundary.tsx, src/components/NotFound.tsx, src/routes/__root.tsx, src/routes/index.tsx, src/routes/login.tsx, src/routes/posts.tsx, src/routes/posts_.$postId.deep.tsx, src/routes/users*.tsx
Reordered Tailwind utility classes for consistency (no functional changes); updated layout and button styling class sequences across multiple components and pages; removed redundant import whitespace.
Template & Legacy Server
public/waitlist-joined-template.html, server/.gitignore
Reformatted HTML email template CSS with proper punctuation and spacing; added Vercel artifact ignore rule to legacy server folder.

Sequence Diagrams

sequenceDiagram
    participant Client as Client Browser
    participant Server as Elysia Server
    participant Auth as Better Auth
    participant Email as Email Service
    participant DB as PostgreSQL

    Client->>Server: POST /api/auth/sign-up with email
    Server->>Auth: betterAuth sign-up handler
    Auth->>DB: Create user record
    DB-->>Auth: User created
    Auth->>Email: Trigger emailOTP plugin
    Email->>Email: Generate OTP code
    Email->>Email: Create HTML template
    Email-->>Client: Send verification email
    
    Client->>Client: User clicks verification link
    Client->>Server: GET /api/auth/verify-email?token=...
    Server->>Auth: Verify OTP token
    Auth->>DB: Update user verified status
    DB-->>Auth: Updated
    Auth-->>Client: Session established
Loading
sequenceDiagram
    participant Client as Authenticated Client
    participant Server as Elysia Server
    participant AuthMW as Auth Middleware
    participant Service as Collection Service
    participant DAO as Collection DAO
    participant DB as PostgreSQL

    Client->>Server: POST /api/collections (with auth token)
    Server->>AuthMW: Derive session from request
    AuthMW->>Auth: getSession(headers)
    Auth-->>AuthMW: {session: {user: {id}}}
    AuthMW-->>Server: Session attached to context
    
    Server->>Service: create(userId, {name, slug, ...})
    Service->>Service: generateSlug() if not provided
    Service->>DAO: create({userId, name, slug, defaults})
    DAO->>DB: INSERT INTO collection VALUES(...)
    DB-->>DAO: Inserted row
    DAO-->>Service: Collection record
    Service-->>Server: Collection record
    Server-->>Client: 201 {data: collection}
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • PR #42: Directly related through auth stack modifications, email transporter setup, Drizzle ORM schema/initialization, and authentication middleware implementations.

Poem

🐰 A server hops into the fray,
With Elysia leading the way,
Collections stored, auth secured tight,
Email OTPs sent just right,
Bun powers the databases with might! 🚀

🚥 Pre-merge checks | ✅ 1 | ❌ 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 'Init mono' is vague and generic, using non-descriptive shorthand that doesn't clearly convey the scope or primary purpose of the substantial changeset. Use a more descriptive title that captures the main objective, such as 'Initialize monorepo structure with Elysia backend' or 'Set up monorepo with server workspace and Turbo configuration'.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch init-mono

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

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

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 14

🧹 Nitpick comments (6)
apps/server/src/types/base-response.type.ts (1)

1-7: Consider adding a discriminator field for cleaner type narrowing.

The current union type requires property checks ("data" in response) to discriminate between success and error cases. Adding a success boolean discriminator would make type narrowing more ergonomic.

Optional improvement
 export type BaseResponse<T> =
 	| {
+			success: true;
 			data: T;
 	  }
 	| {
+			success: false;
 			message: string;
 	  };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/server/src/types/base-response.type.ts` around lines 1 - 7, The
BaseResponse<T> union lacks a discriminator, making narrowing awkward; change
BaseResponse<T> to use a boolean discriminator (e.g., replace the two union
branches with { success: true; data: T } | { success: false; message: string })
so consumers can narrow by checking response.success, and update any call sites
or type annotations that construct or inspect BaseResponse<T> (look for usages
of BaseResponse, response.data, and "data" in response checks) to rely on
response.success instead.
apps/server/README.md (1)

3-9: "Getting Started" section is misleading for this project.

The bun create elysia ./elysia-example command scaffolds a brand new Elysia project. For an existing project README, this section should instead document how to set up and run this project, such as:

bun install

And any required environment variable setup.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/server/README.md` around lines 3 - 9, The "Getting Started" heading in
README.md currently shows a scaffold command for creating a new Elysia project
which is misleading for this repository; update the Getting Started section to
document how to set up and run this existing project instead (replace `bun
create elysia ./elysia-example` with project-specific steps such as `bun
install`, required environment variable names and example values, and the
commands to run the app for development and production like `bun run dev` / `bun
build && bun start`), and enumerate any required env vars and where to configure
them (e.g., .env), referencing the README's "Getting Started" heading so
reviewers can locate the change.
apps/server/tsconfig.json (1)

13-13: Consider disabling allowJs for tighter type safety.

Unless you’re intentionally compiling JS files, this weakens strict-mode guarantees in a fresh TS server package.

♻️ Proposed change
-		"allowJs": true,
+		"allowJs": false,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/server/tsconfig.json` at line 13, The tsconfig currently has "allowJs":
true which weakens TypeScript strictness; change the tsconfig.json to disable
JavaScript compilation (set "allowJs" to false or remove the "allowJs" property)
so the TypeScript compiler only processes .ts/.tsx files and restores stricter
type guarantees—update the "allowJs" setting in the existing tsconfig.json entry
to false (or delete the entry) and run a quick build to catch any resulting type
errors.
apps/server/src/routes/collection/dao.ts (1)

20-22: Authorization is enforced at the route layer, but the pattern is inconsistent with write operations.

The route handler at /:id (lines 49–50 in index.ts) does enforce ownership checks before returning private collections. However, the implementation pattern differs from update() and delete(), which scope their queries by userId at the DAO layer. For consistency and efficiency, consider updating get() to accept an optional userId parameter and scope the DAO query accordingly (similar to update() and delete()), or explicitly document this route-layer authorization pattern if it is intentional.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/server/src/routes/collection/dao.ts` around lines 20 - 22, get() in the
collection DAO currently fetches by id only, which is inconsistent with update()
and delete() that scope by userId; change get(id: string) to get(id: string,
userId?: string) and, when userId is provided, include eq(collection.userId,
userId) in the where clause so private/owned collections are enforced at the DAO
layer (mirroring update() and delete()); ensure callers that rely on route-layer
auth pass the userId when appropriate or keep route behavior unchanged if
intentional.
apps/server/src/lib/auth/index.ts (1)

64-65: Avoid any casts in exported OpenAPI contract.

Lines 64-65 cast both fields to Promise<any>, which hides schema-shape regressions. Prefer explicit return types from generateOpenAPISchema() extraction.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/server/src/lib/auth/index.ts` around lines 64 - 65, The exported OpenAPI
contract is using Promise<any> which hides schema regressions; replace those
casts with explicit OpenAPI types (e.g., use OpenAPIV3.OpenAPIObject or
OpenAPIV3.PathsObject / OpenAPIV3.ComponentsObject) by importing OpenAPIV3 from
'openapi-types' and updating the promises returned by getSchema() and the
extraction lines (the components extraction and the other field currently cast
to Promise<any>) to return the correct Promise<...> types; if needed, update
generateOpenAPISchema() (or the function that returns the schema) to have a
typed return signature so getSchema().then(({ components }) => components) and
the other extraction no longer require any casts.
apps/server/src/routes/collection/index.ts (1)

13-13: Centralize repeated unauthorized checks.

The same if (!session) return status(401, ...) block appears in multiple handlers. Extract a shared guard/helper to keep auth behavior consistent and reduce drift across endpoints.

Also applies to: 29-29, 67-67, 87-87

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/server/src/routes/collection/index.ts` at line 13, Multiple handlers
repeat the same session check ("if (!session) return status(401, { message:
'Unauthorized' })"); extract a single helper (e.g., requireSession or
assertSession) that accepts the handler's request/locals or the session value
and either returns the session or throws/returns the 401 response, then replace
every inline check in the collection route handlers (the locations with the
repeated check at lines like 13, 29, 67, 87) to call this helper so auth
behavior is centralized and consistent across all endpoints.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.oxfmtrc.json:
- Line 19: The .oxfmtrc.json currently contains invalid JavaScript-style
comments (the lines starting with //, e.g. the commented entry "stylesheet":
["./www/src/app/globals.css"], and the other // lines) which break JSON parsing;
remove the leading // and the commented lines entirely (or delete the
corresponding keys instead) so the file is valid JSON—locate the commented
entries by searching for the exact string "stylesheet":
["./www/src/app/globals.css"] and any other lines beginning with // and delete
them.

In `@apps/server/package.json`:
- Around line 5-9: The package.json currently sets the "module" field to
"src/index.js" which doesn't exist; update the package.json "module" entry to
point to the built output "dist/index.js" (since tsdown emits to dist by
default) or remove the "module" field entirely because this is an app-only
server package (adjust the "module" key in package.json accordingly).

In `@apps/server/src/db/schema.ts`:
- Line 150: The schema changed collection.id to uuid but bookmark.collectionId
remains text, causing an incompatible FK; update the bookmark table definition
(the bookmark.collectionId column) to use uuid instead of text and make it
reference collection.id (e.g., references(() => collection.id, { onDelete: "set
null" })) so the column types match; ensure the uuid helper is used/imported
where bookmark.collectionId is declared and retain the onDelete behavior.

In `@apps/server/src/env.ts`:
- Line 13: The DATABASE_URL schema currently uses z.string() which allows empty
strings; update the env schema so DATABASE_URL is validated as non-empty (use
z.string().min(1) consistent with other secrets) so the app fails fast on a
blank DATABASE_URL value; locate the DATABASE_URL entry in the env schema and
replace the z.string() validator with z.string().min(1).

In `@apps/server/src/lib/auth/index.ts`:
- Around line 58-61: The loop that sets operation.tags currently iterates all
keys on paths[path] and assumes each is an HTTP operation; narrow the mutation
to actual HTTP method keys (e.g., "get", "post", "put", "patch", "delete",
"options", "head") to avoid touching non-operation entries. In the block where
you iterate Object.keys(paths[path]) (using variables path, method, reference,
key, operation), add a guard that checks method is one of the allowed HTTP verbs
before casting (reference[key] as any)[method] and assigning operation.tags =
["Better Auth"] so only real operation objects are mutated.

In `@apps/server/src/middlewares/auth.ts`:
- Around line 5-7: The auth middleware should catch exceptions from
auth.api.getSession so auth failures don't become 500s; inside authMiddleware's
derive handler wrap the call to auth.api.getSession({ headers: request.headers
}) in a try/catch and on any error return { session: null } (or the safe
fallback) so unauthenticated requests are handled gracefully while preserving
normal session return when no error occurs.

In `@apps/server/src/routes/collection/index.ts`:
- Around line 49-51: The route currently returns status(403, ...) when a private
collection exists but the caller is not the owner (the if block checking
found.privacy === "private" && found.userId !== session?.user.id); change this
to return status(404, { message: "Not Found" }) instead to avoid leaking
existence—locate the check in the collection route handler where found and
session are compared and replace the 403 response with a 404 response while
keeping the same message semantics.

In `@apps/server/src/routes/collection/model.ts`:
- Line 23: The updateBody codec (currently defined as updateBody:
t.Partial(t.Pick(insertSchema, collectionFields))) allows an empty object which
later causes db.update().set({}) and invalid SQL; change updateBody to reject
empty updates by enforcing at least one mutable field (minProperties: 1) — e.g.,
replace the current codec with a wrapper/refinement around
t.Partial(t.Pick(insertSchema, collectionFields)) that validates the object has
one or more keys (or use a codec option supporting minProperties), so
updateBody, insertSchema and collectionFields remain referenced but empty {}
inputs are rejected at validation time.

In `@apps/server/src/routes/collection/service.ts`:
- Around line 10-20: The create method currently inserts a slug directly (slug
variable) via CollectionDAO.create and will surface raw DB unique-key errors;
update CollectionService.create to catch uniqueness conflicts from
CollectionDAO.create, and handle two cases: if input.slug was provided,
translate the DB error into a domain-level conflict error (e.g., throw a
controlled ConflictError for duplicate slug); if the slug was auto-generated via
generateSlug, implement a retry loop (call generateSlug with a suffix or
increment a counter and attempt CollectionDAO.create again) for a few attempts,
and only throw the domain conflict after exhausting retries; reference the
create method, slug variable, generateSlug, and CollectionDAO.create when making
these changes.

In `@apps/server/src/routes/collection/utils.ts`:
- Around line 9-12: generateSlug currently appends a random suffix to the output
of toSlug(name) but doesn’t handle when toSlug returns an empty string,
producing slugs like "-abcde"; update generateSlug to detect an empty base (from
toSlug(name)) and replace it with a safe fallback (e.g., "untitled" or "item")
before appending the suffix so the final slug always has a valid leading
segment; ensure you modify the generateSlug function (and preserve the suffix
logic that uses Math.random().toString(36).slice(2,7)) so the returned value is
always "fallback-xxxxx" when toSlug yields "".

In `@package.json`:
- Around line 8-29: The repo root has an orphaned frontend in the "src/" folder
referenced by files like src/routes/__root.tsx, src/lib/supabase.ts and
src/db/index.ts but the root package.json workspaces only include "apps/*", so
unresolved imports occur; fix by either (A) moving the frontend into a workspace
(create apps/web with its own package.json listing required deps such as react,
`@tanstack/react-router`, `@tanstack/react-query`, `@supabase/supabase-js`,
drizzle-orm, postgres, etc., and update root "workspaces" to include "apps/*" so
the web app is installed and built with turbo), or (B) remove the orphaned src/
directory entirely if the frontend is not needed; ensure scripts (dev/build) and
any references to the frontend are updated/removed accordingly.

In `@server/.gitignore`:
- Line 1: Remove the redundant .gitignore in the server directory: delete this
file or, if any rules inside are still required, merge them into the repository
root .gitignore (or the active app's .gitignore such as apps/server/.gitignore)
and then remove server/.gitignore; ensure the .vercel entry remains in the root
.gitignore so the rule is preserved after deletion.

In `@turbo.json`:
- Around line 26-29: The "db:migrate" task is incorrectly marked persistent:
true; update the turbo task configuration for the "db:migrate" entry to remove
the persistent flag or set "persistent" to false so migrations run as one-shot
processes (leave persistent:true only for long-running tasks like "db:studio" or
"db:watch"); locate the "db:migrate" object in the turbo.json and modify its
properties accordingly.
- Around line 20-47: turbo.json declares database tasks (db:push, db:generate,
db:migrate, db:studio, db:start, db:stop, db:watch, db:down) that do not exist
in any workspace package scripts, and db:migrate is incorrectly marked
persistent: true; either add matching scripts for these task names to the
appropriate package.json (e.g., implement "db:push", "db:migrate", etc., in the
root or apps/server package.json) or remove these task entries from turbo.json,
and change/remove persistent: true from the db:migrate and other one-shot tasks
so migrations remain non-persistent.

---

Nitpick comments:
In `@apps/server/README.md`:
- Around line 3-9: The "Getting Started" heading in README.md currently shows a
scaffold command for creating a new Elysia project which is misleading for this
repository; update the Getting Started section to document how to set up and run
this existing project instead (replace `bun create elysia ./elysia-example` with
project-specific steps such as `bun install`, required environment variable
names and example values, and the commands to run the app for development and
production like `bun run dev` / `bun build && bun start`), and enumerate any
required env vars and where to configure them (e.g., .env), referencing the
README's "Getting Started" heading so reviewers can locate the change.

In `@apps/server/src/lib/auth/index.ts`:
- Around line 64-65: The exported OpenAPI contract is using Promise<any> which
hides schema regressions; replace those casts with explicit OpenAPI types (e.g.,
use OpenAPIV3.OpenAPIObject or OpenAPIV3.PathsObject /
OpenAPIV3.ComponentsObject) by importing OpenAPIV3 from 'openapi-types' and
updating the promises returned by getSchema() and the extraction lines (the
components extraction and the other field currently cast to Promise<any>) to
return the correct Promise<...> types; if needed, update generateOpenAPISchema()
(or the function that returns the schema) to have a typed return signature so
getSchema().then(({ components }) => components) and the other extraction no
longer require any casts.

In `@apps/server/src/routes/collection/dao.ts`:
- Around line 20-22: get() in the collection DAO currently fetches by id only,
which is inconsistent with update() and delete() that scope by userId; change
get(id: string) to get(id: string, userId?: string) and, when userId is
provided, include eq(collection.userId, userId) in the where clause so
private/owned collections are enforced at the DAO layer (mirroring update() and
delete()); ensure callers that rely on route-layer auth pass the userId when
appropriate or keep route behavior unchanged if intentional.

In `@apps/server/src/routes/collection/index.ts`:
- Line 13: Multiple handlers repeat the same session check ("if (!session)
return status(401, { message: 'Unauthorized' })"); extract a single helper
(e.g., requireSession or assertSession) that accepts the handler's
request/locals or the session value and either returns the session or
throws/returns the 401 response, then replace every inline check in the
collection route handlers (the locations with the repeated check at lines like
13, 29, 67, 87) to call this helper so auth behavior is centralized and
consistent across all endpoints.

In `@apps/server/src/types/base-response.type.ts`:
- Around line 1-7: The BaseResponse<T> union lacks a discriminator, making
narrowing awkward; change BaseResponse<T> to use a boolean discriminator (e.g.,
replace the two union branches with { success: true; data: T } | { success:
false; message: string }) so consumers can narrow by checking response.success,
and update any call sites or type annotations that construct or inspect
BaseResponse<T> (look for usages of BaseResponse, response.data, and "data" in
response checks) to rely on response.success instead.

In `@apps/server/tsconfig.json`:
- Line 13: The tsconfig currently has "allowJs": true which weakens TypeScript
strictness; change the tsconfig.json to disable JavaScript compilation (set
"allowJs" to false or remove the "allowJs" property) so the TypeScript compiler
only processes .ts/.tsx files and restores stricter type guarantees—update the
"allowJs" setting in the existing tsconfig.json entry to false (or delete the
entry) and run a quick build to catch any resulting type errors.
🪄 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: 2936ac86-5d26-49bc-836b-8ea4fe3fe9f3

📥 Commits

Reviewing files that changed from the base of the PR and between 53e7ab7 and e88bbba.

⛔ Files ignored due to path filters (2)
  • apps/server/public/icon-light.png is excluded by !**/*.png
  • bun.lock is excluded by !**/*.lock
📒 Files selected for processing (41)
  • .gitignore
  • .oxfmtrc.json
  • TODO.md
  • apps/server/.gitignore
  • apps/server/README.md
  • apps/server/package.json
  • apps/server/src/db/index.ts
  • apps/server/src/db/schema.ts
  • apps/server/src/env.ts
  • apps/server/src/index.ts
  • apps/server/src/lib/auth/index.ts
  • apps/server/src/lib/email/index.ts
  • apps/server/src/lib/email/transporter.ts
  • apps/server/src/lib/supabase.ts
  • apps/server/src/middlewares/auth.ts
  • apps/server/src/middlewares/logger.ts
  • apps/server/src/routes/collection/dao.ts
  • apps/server/src/routes/collection/index.ts
  • apps/server/src/routes/collection/model.ts
  • apps/server/src/routes/collection/service.ts
  • apps/server/src/routes/collection/utils.ts
  • apps/server/src/types/base-response.type.ts
  • apps/server/src/types/email.types.ts
  • apps/server/tsconfig.json
  • apps/server/tsdown.config.ts
  • apps/server/vercel.json
  • package.json
  • public/waitlist-joined-template.html
  • server/.gitignore
  • src/components/DefaultCatchBoundary.tsx
  • src/components/NotFound.tsx
  • src/routes/__root.tsx
  • src/routes/api/join-wishlist.ts
  • src/routes/index.tsx
  • src/routes/login.tsx
  • src/routes/posts.tsx
  • src/routes/posts_.$postId.deep.tsx
  • src/routes/users.$userId.tsx
  • src/routes/users.index.tsx
  • src/routes/users.tsx
  • turbo.json
💤 Files with no reviewable changes (1)
  • src/routes/__root.tsx

"options": {
"experimentalTailwindcss": {
"stylesheet": ["./www/src/app/globals.css"],
// "stylesheet": ["./www/src/app/globals.css"],
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 | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify if the JSON file is valid
cat .oxfmtrc.json | jq . 2>&1 || echo "JSON parse failed"

Repository: husamql3/keeply

Length of output: 134


Remove JavaScript comments from JSON configuration file.

The .oxfmtrc.json file contains // comments on lines 19 and 31, which are invalid JSON syntax. JSON parsers will fail when attempting to read this configuration, breaking the OxFmt formatter.

Either remove these lines entirely or use a different approach to disable options (e.g., by removing the property keys rather than commenting them out).

Proposed fix
 			"options": {
 				"experimentalTailwindcss": {
-					// "stylesheet": ["./www/src/app/globals.css"],
 					"attributes": ["class", "className"],
 	"experimentalSortImports": {
 		"groups": [
-			// ["side-effect"],
 			["builtin"],
📝 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
// "stylesheet": ["./www/src/app/globals.css"],
"attributes": ["class", "className"],
🧰 Tools
🪛 Biome (2.4.10)

[error] 19-19: Expected a property but instead found '// "stylesheet": ["./www/src/app/globals.css"],'.

(parse)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.oxfmtrc.json at line 19, The .oxfmtrc.json currently contains invalid
JavaScript-style comments (the lines starting with //, e.g. the commented entry
"stylesheet": ["./www/src/app/globals.css"], and the other // lines) which break
JSON parsing; remove the leading // and the commented lines entirely (or delete
the corresponding keys instead) so the file is valid JSON—locate the commented
entries by searching for the exact string "stylesheet":
["./www/src/app/globals.css"] and any other lines beginning with // and delete
them.

Comment on lines +5 to +9
"module": "src/index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"dev": "bun run --watch src/index.ts",
"build": "tsdown"
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 | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
python - <<'PY'
import json
from pathlib import Path

pkg = json.loads(Path("apps/server/package.json").read_text())
module_path = Path("apps/server") / pkg["module"]

print(f"module -> {module_path}")
print(f"exists -> {module_path.exists()}")

for config in Path("apps/server").glob("tsdown.config.ts"):
    print(f"\n=== {config} ===")
    print(config.read_text())
PY

Repository: husamql3/keeply

Length of output: 196


🏁 Script executed:

cat -n apps/server/package.json

Repository: husamql3/keeply

Length of output: 1059


🏁 Script executed:

cat -n apps/server/tsdown.config.ts

Repository: husamql3/keeply

Length of output: 127


🏁 Script executed:

# Check if there's a dist or build directory
ls -la apps/server/ | grep -E '(dist|build|out)'

Repository: husamql3/keeply

Length of output: 41


🌐 Web query:

tsdown default output directory configuration

💡 Result:

tsdown’s default output directory is dist (the outDir option defaults to 'dist') [1].

Change the output directory

1) In tsdown.config.ts / tsdown.config.js:

import { defineConfig } from 'tsdown'

export default defineConfig({
  outDir: 'lib',
})

(outDir is a top-level config option) [1].

2) Via CLI:

tsdown -d lib
# or
tsdown --out-dir lib

(-d, --out-dir <dir> sets the output directory; --outDir and --out-dir are equivalent) [2].

Related default behavior

By default, tsdown cleans the output directory (outDir) before each build (unless you disable it with --no-clean) [3].

Sources: [1] [2] [3]


module field points to a non-existent file.

Line 5 advertises src/index.js, but this file does not exist. Since tsdown builds to dist/ by default (and no custom outDir is configured), the module entry should be dist/index.js. Alternatively, remove the module field entirely since this is an app-only server package, not a library.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/server/package.json` around lines 5 - 9, The package.json currently sets
the "module" field to "src/index.js" which doesn't exist; update the
package.json "module" entry to point to the built output "dist/index.js" (since
tsdown emits to dist by default) or remove the "module" field entirely because
this is an app-only server package (adjust the "module" key in package.json
accordingly).

NODE_ENV: z.enum(["development", "production"]),

// database
DATABASE_URL: z.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.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

cat -n apps/server/src/env.ts

Repository: husamql3/keeply

Length of output: 1458


🏁 Script executed:

rg -i "database.*url\|postgres\|mysql" apps/server/src --type ts --type js -B 2 -A 2 | head -50

Repository: husamql3/keeply

Length of output: 41


Add validation to DATABASE_URL to fail fast on blank configuration.

z.string() accepts empty strings, so a present-but-empty env var passes validation and only fails later when the database client connects. Tighten this to .min(1) to match the pattern used for other required secrets in this file.

Suggested fix
-		DATABASE_URL: z.string(),
+		DATABASE_URL: z.string().min(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
DATABASE_URL: z.string(),
DATABASE_URL: z.string().min(1),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/server/src/env.ts` at line 13, The DATABASE_URL schema currently uses
z.string() which allows empty strings; update the env schema so DATABASE_URL is
validated as non-empty (use z.string().min(1) consistent with other secrets) so
the app fails fast on a blank DATABASE_URL value; locate the DATABASE_URL entry
in the env schema and replace the z.string() validator with z.string().min(1).

Comment on lines +58 to +61
for (const method of Object.keys(paths[path])) {
const operation = (reference[key] as any)[method];
operation.tags = ["Better Auth"];
}
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 | 🟠 Major

Guard tag injection to operation objects only.

Line 58 iterates every key in a path item, but Line 60 assumes each entry is an operation object. If any non-operation key appears, this can throw or mutate the wrong structure. Restrict tagging to HTTP method keys only.

💡 Suggested fix
+const HTTP_METHODS = new Set([
+	"get",
+	"post",
+	"put",
+	"patch",
+	"delete",
+	"options",
+	"head",
+	"trace",
+]);
+
 for (const path of Object.keys(paths)) {
 	const key = prefix + path;
 	reference[key] = paths[path];
-	for (const method of Object.keys(paths[path])) {
-		const operation = (reference[key] as any)[method];
-		operation.tags = ["Better Auth"];
+	for (const method of Object.keys(paths[path])) {
+		if (!HTTP_METHODS.has(method)) continue;
+		const operation = (reference[key] as any)[method];
+		operation.tags = ["Better Auth"];
 	}
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/server/src/lib/auth/index.ts` around lines 58 - 61, The loop that sets
operation.tags currently iterates all keys on paths[path] and assumes each is an
HTTP operation; narrow the mutation to actual HTTP method keys (e.g., "get",
"post", "put", "patch", "delete", "options", "head") to avoid touching
non-operation entries. In the block where you iterate Object.keys(paths[path])
(using variables path, method, reference, key, operation), add a guard that
checks method is one of the allowed HTTP verbs before casting (reference[key] as
any)[method] and assigning operation.tags = ["Better Auth"] so only real
operation objects are mutated.

Comment on lines +9 to +12
export function generateSlug(name: string): string {
const base = toSlug(name);
const suffix = Math.random().toString(36).slice(2, 7);
return `${base}-${suffix}`;
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 | 🟡 Minor

Guard against empty base slugs before appending suffix.

When toSlug(name) returns "", current output becomes -xxxxx. Add a fallback base to keep slug format valid.

🔧 Proposed fix
 export function generateSlug(name: string): string {
-	const base = toSlug(name);
+	const base = toSlug(name) || "collection";
 	const suffix = Math.random().toString(36).slice(2, 7);
 	return `${base}-${suffix}`;
 }
📝 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
export function generateSlug(name: string): string {
const base = toSlug(name);
const suffix = Math.random().toString(36).slice(2, 7);
return `${base}-${suffix}`;
export function generateSlug(name: string): string {
const base = toSlug(name) || "collection";
const suffix = Math.random().toString(36).slice(2, 7);
return `${base}-${suffix}`;
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/server/src/routes/collection/utils.ts` around lines 9 - 12, generateSlug
currently appends a random suffix to the output of toSlug(name) but doesn’t
handle when toSlug returns an empty string, producing slugs like "-abcde";
update generateSlug to detect an empty base (from toSlug(name)) and replace it
with a safe fallback (e.g., "untitled" or "item") before appending the suffix so
the final slug always has a valid leading segment; ensure you modify the
generateSlug function (and preserve the suffix logic that uses
Math.random().toString(36).slice(2,7)) so the returned value is always
"fallback-xxxxx" when toSlug yields "".

Comment on lines +8 to +29
"workspaces": [
"apps/*"
],
"type": "module",
"sideEffects": false,
"scripts": {
"dev": "vite dev",
"build": "vite build && tsc --noEmit",
"preview": "vite preview",
"start": "node .output/server/index.mjs",
"format": "oxlint && oxfmt --write",
"db:push": "drizzle-kit push",
"db:pull": "drizzle-kit pull",
"db:studio": "npx db-studio@latest -e .env",
"db:generate": "drizzle-kit generate",
"db:migrate": "drizzle-kit migrate",
"db:reset": "drizzle-kit reset"
"dev": "turbo dev",
"dev:server": "turbo -F server dev",
"build": "turbo build",
"build:server": "turbo -F server build",
"format": "bunx oxlint && bunx oxfmt --write"
},
"dependencies": {
"@hookform/resolvers": "^5.2.2",
"@sentry/node": "^10.47.0",
"@sentry/react": "^10.47.0",
"@sentry/vite-plugin": "^5.1.1",
"@supabase/supabase-js": "^2.101.1",
"@t3-oss/env-core": "^0.13.11",
"@tabler/icons-react": "^3.41.1",
"@tanstack/react-query": "^5.96.2",
"@tanstack/react-query-devtools": "^5.96.2",
"@tanstack/react-router": "^1.168.10",
"@tanstack/react-router-devtools": "^1.166.11",
"@tanstack/react-start": "^1.167.16",
"@types/nodemailer": "^7.0.11",
"better-auth": "^1.5.6",
"class-variance-authority": "^0.7.1",
"dotenv": "^17.4.0",
"drizzle-orm": "^0.45.2",
"node-cron": "^4.2.1",
"nodemailer": "^8.0.4",
"ogl": "^1.0.11",
"postgres": "^3.4.8",
"radix-ui": "^1.4.3",
"react": "^19.2.4",
"react-dom": "^19.2.4",
"react-hook-form": "^7.72.1",
"sileo": "^0.1.5",
"tailwind-merge": "^2.6.1",
"zod": "^4.3.6"
"@better-auth/infra": "^0.1.14",
"elysia": "^1.4.28"
},
"devDependencies": {
"@tailwindcss/vite": "^4.2.2",
"@tanstack/eslint-plugin-query": "^5.96.2",
"@types/bun": "^1.3.4",
"@types/node": "^22.19.17",
"@types/node-cron": "^3.0.11",
"@types/react": "^19.2.14",
"@types/react-dom": "^19.2.3",
"@vitejs/plugin-react": "^6.0.1",
"drizzle-kit": "^0.31.10",
"nitro": "^3.0.260311-beta",
"shadcn": "^3.8.5",
"tailwindcss": "^4.2.2",
"tw-animate-css": "^1.4.0",
"vite": "^8.0.3"
}
"tsdown": "^0.21.7",
"turbo": "^2.8.17",
"typescript": "^5.8.2"
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 | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
python - <<'PY'
import json
import pathlib
import re

pkg = json.loads(pathlib.Path("package.json").read_text())
declared = set(pkg.get("dependencies", {})) | set(pkg.get("devDependencies", {}))

targets = [
    pathlib.Path("src/routes/__root.tsx"),
    pathlib.Path("src/router.tsx"),
    pathlib.Path("src/lib/supabase.ts"),
    pathlib.Path("src/db/index.ts"),
]

pattern = re.compile(r'^\s*import(?:.+?\sfrom\s+)?["\']([^"\']+)["\']')

def package_name(spec: str):
    if spec.startswith((".", "@/")):
        return None
    return "/".join(spec.split("/")[:2]) if spec.startswith("@") else spec.split("/")[0]

for path in targets:
    print(f"\n=== {path} ===")
    if not path.exists():
        print("MISSING")
        continue
    for i, line in enumerate(path.read_text().splitlines(), 1):
        match = pattern.search(line)
        if not match:
            continue
        name = package_name(match.group(1))
        if name and name not in declared:
            print(f"Line {i}: missing manifest dependency -> {name}")
PY

Repository: husamql3/keeply

Length of output: 726


🏁 Script executed:

git ls-files src/ | head -20

Repository: husamql3/keeply

Length of output: 584


🏁 Script executed:

git ls-files apps/ | head -30

Repository: husamql3/keeply

Length of output: 858


🏁 Script executed:

cat -n package.json | sed -n '1,50p'

Repository: husamql3/keeply

Length of output: 1107


🏁 Script executed:

ls -la apps/

Repository: husamql3/keeply

Length of output: 225


🏁 Script executed:

cat tsconfig.json 2>/dev/null || echo "No tsconfig.json in root"

Repository: husamql3/keeply

Length of output: 512


🏁 Script executed:

git ls-files | grep -E '^apps/[^/]+/?$' | sort -u

Repository: husamql3/keeply

Length of output: 41


The root frontend (src/) is orphaned outside the monorepo workspace.

The root src/ directory still exists with imports for React, TanStack Router/Query, Supabase, and Drizzle, but the workspace configuration only includes apps/server. The root package.json declares neither runtime dependencies nor a build step for this code. When running npm run build or npm run dev, the root app will fail with unresolved imports:

  • @tanstack/react-router, @tanstack/react-query, react (from src/routes/__root.tsx)
  • @supabase/supabase-js (from src/lib/supabase.ts)
  • drizzle-orm, postgres (from src/db/index.ts)
  • Plus others: @sentry/react, @tanstack/react-router-devtools, sileo

Either move the root frontend into its own workspace (e.g., apps/web) with its own package.json, or remove the orphaned src/ directory entirely.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@package.json` around lines 8 - 29, The repo root has an orphaned frontend in
the "src/" folder referenced by files like src/routes/__root.tsx,
src/lib/supabase.ts and src/db/index.ts but the root package.json workspaces
only include "apps/*", so unresolved imports occur; fix by either (A) moving the
frontend into a workspace (create apps/web with its own package.json listing
required deps such as react, `@tanstack/react-router`, `@tanstack/react-query`,
`@supabase/supabase-js`, drizzle-orm, postgres, etc., and update root "workspaces"
to include "apps/*" so the web app is installed and built with turbo), or (B)
remove the orphaned src/ directory entirely if the frontend is not needed;
ensure scripts (dev/build) and any references to the frontend are
updated/removed accordingly.

@@ -0,0 +1 @@
.vercel
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 | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check if there's a gitignore in apps/server and verify directory structure
echo "=== Checking for .gitignore files ==="
fd -H '.gitignore' --type f

echo ""
echo "=== Checking directory structure for server locations ==="
tree -L 2 -d 2>/dev/null || find . -type d -maxdepth 2 | head -30

Repository: husamql3/keeply

Length of output: 457


🏁 Script executed:

#!/bin/bash

echo "=== Contents of server/.gitignore ==="
cat -n server/.gitignore

echo ""
echo "=== Contents of apps/server/.gitignore ==="
cat -n apps/server/.gitignore

echo ""
echo "=== What's inside server/ directory ==="
ls -la server/

echo ""
echo "=== What's inside apps/server/ directory ==="
ls -la apps/server/

Repository: husamql3/keeply

Length of output: 1843


🏁 Script executed:

cat -n .gitignore

Repository: husamql3/keeply

Length of output: 465


Remove this redundant .gitignore file.

The .vercel entry is already ignored at the repository root level (.gitignore:8), making this file unnecessary. Additionally, server/ appears to be a minimal/deprecated directory compared to the active apps/server/ application. If server/ and its contents are still needed, consolidate the .gitignore rules into the appropriate location instead.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/.gitignore` at line 1, Remove the redundant .gitignore in the server
directory: delete this file or, if any rules inside are still required, merge
them into the repository root .gitignore (or the active app's .gitignore such as
apps/server/.gitignore) and then remove server/.gitignore; ensure the .vercel
entry remains in the root .gitignore so the rule is preserved after deletion.

Comment on lines +20 to +47
"db:push": {
"cache": false
},
"db:generate": {
"cache": false
},
"db:migrate": {
"cache": false,
"persistent": true
},
"db:studio": {
"cache": false,
"persistent": true
},
"db:start": {
"cache": false,
"persistent": true
},
"db:stop": {
"cache": false
},
"db:watch": {
"cache": false,
"persistent": true
},
"db:down": {
"cache": false
}
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 | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify which packages have db:* scripts defined
echo "=== Checking for db:* scripts in package.json files ==="
fd 'package.json' --type f --exec grep -l '"db:' {} \; 2>/dev/null || echo "No db:* scripts found in any package.json"

echo ""
echo "=== apps/server/package.json scripts section ==="
cat apps/server/package.json 2>/dev/null | jq '.scripts' || cat apps/server/package.json

Repository: husamql3/keeply

Length of output: 328


🏁 Script executed:

cat turbo.json | head -60

Repository: husamql3/keeply

Length of output: 819


🏁 Script executed:

fd 'package.json' --type f | head -20

Repository: husamql3/keeply

Length of output: 96


🏁 Script executed:

cat package.json | jq '.scripts'

Repository: husamql3/keeply

Length of output: 240


Database scripts defined in turbo.json are missing from all workspace packages.

The turbo.json defines 8 database-related tasks (db:push, db:generate, db:migrate, db:studio, db:start, db:stop, db:watch, db:down), but neither the root package.json nor apps/server/package.json contain any of these scripts. Turbo will silently skip these tasks when run, which could confuse developers.

Additionally, db:migrate is marked with persistent: true, which is unusual for a migration task—migrations are typically one-shot operations.

Either add the corresponding scripts to the appropriate package.json or remove these task definitions from turbo.json until the scripts are implemented.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@turbo.json` around lines 20 - 47, turbo.json declares database tasks
(db:push, db:generate, db:migrate, db:studio, db:start, db:stop, db:watch,
db:down) that do not exist in any workspace package scripts, and db:migrate is
incorrectly marked persistent: true; either add matching scripts for these task
names to the appropriate package.json (e.g., implement "db:push", "db:migrate",
etc., in the root or apps/server package.json) or remove these task entries from
turbo.json, and change/remove persistent: true from the db:migrate and other
one-shot tasks so migrations remain non-persistent.

Comment on lines +26 to +29
"db:migrate": {
"cache": false,
"persistent": true
},
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 | 🟡 Minor

db:migrate should not be marked as persistent: true.

Database migrations are typically one-shot operations that run to completion and exit. Marking them as persistent: true suggests a long-running process, which is incorrect for migrations. This setting is appropriate for db:studio (interactive UI) and db:watch (file watching), but not for migrations.

Proposed fix
 		"db:migrate": {
-			"cache": false,
-			"persistent": true
+			"cache": false
 		},
📝 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
"db:migrate": {
"cache": false,
"persistent": true
},
"db:migrate": {
"cache": false
},
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@turbo.json` around lines 26 - 29, The "db:migrate" task is incorrectly marked
persistent: true; update the turbo task configuration for the "db:migrate" entry
to remove the persistent flag or set "persistent" to false so migrations run as
one-shot processes (leave persistent:true only for long-running tasks like
"db:studio" or "db:watch"); locate the "db:migrate" object in the turbo.json and
modify its properties accordingly.

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