Skip to content

crm modiru.com#1

Merged
Qomserver merged 4 commits intomainfrom
cursor/develop-a-full-featured-sales-crm-system-a8cf
Aug 10, 2025
Merged

crm modiru.com#1
Qomserver merged 4 commits intomainfrom
cursor/develop-a-full-featured-sales-crm-system-a8cf

Conversation

@Qomserver
Copy link
Owner

@Qomserver Qomserver commented Aug 9, 2025

User description

Initial scaffolding and core implementation of a production-ready CRM suite with Next.js, Prisma, and essential modules.

This PR establishes the foundational architecture for the requested CRM system, including a Next.js 14 application with TypeScript, Tailwind RTL, Prisma (PostgreSQL), JWT authentication, and RBAC. It sets up a comprehensive database schema, core API routes, and initial UI skeletons for key modules like Dashboard, Leads, and Orders, all designed for a full-screen, responsive Persian user experience. Dockerization is also included for streamlined development and deployment.


Open in Cursor Open in Web

PR Type

Enhancement


Description

  • Complete CRM system scaffolding with Next.js 14

  • Comprehensive database schema with Prisma ORM

  • JWT authentication and RBAC implementation

  • Persian RTL UI with dashboard and modules


Diagram Walkthrough

flowchart LR
  A["Next.js 14 App"] --> B["Prisma Database"]
  A --> C["JWT Auth System"]
  A --> D["Persian RTL UI"]
  B --> E["Lead Management"]
  B --> F["Order Tracking"]
  B --> G["User Roles"]
  D --> H["Dashboard"]
  D --> I["Reports"]
  D --> J["Settings"]
Loading

File Walkthrough

Relevant files
Enhancement
13 files
schema.prisma
Comprehensive database schema with 25+ models                       
+341/-0 
page.tsx
Persian login form with validation                                             
+75/-0   
layout.tsx
Dashboard layout with Persian navigation                                 
+37/-0   
page.tsx
Lead management table interface                                                   
+65/-0   
page.tsx
Order listing with customer details                                           
+46/-0   
route.ts
JWT authentication API endpoint                                                   
+16/-0   
route.ts
Lead CRUD API operations                                                                 
+29/-0   
overview.tsx
Dashboard overview with Persian calendar                                 
+108/-0 
auth.ts
JWT token and password utilities                                                 
+28/-0   
middleware.ts
Authentication middleware with RTL headers                             
+39/-0   
page.tsx
Settings page with configuration sections                               
+42/-0   
page.tsx
Reports page placeholder structure                                             
+23/-0   
page.tsx
Message management interface placeholder                                 
+23/-0   
Configuration changes
6 files
package.json
Project dependencies and build scripts                                     
+50/-0   
tailwind.config.ts
Tailwind configuration with RTL support                                   
+33/-0   
Dockerfile
Multi-stage Docker build configuration                                     
+21/-0   
docker-compose.yml
PostgreSQL and web service orchestration                                 
+24/-0   
.env
Environment variables for database and JWT                             
+10/-0   
seed.ts
Database seeding with admin user                                                 
+24/-0   
Formatting
1 files
globals.css
RTL styling with Persian font                                                       
+30/-0   
Additional files
18 files
.eslintrc.json +3/-0     
README.md +36/-0   
next.config.mjs +4/-0     
postcss.config.mjs +8/-0     
page.tsx +48/-0   
page.tsx +34/-0   
page.tsx +16/-0   
page.tsx +9/-0     
route.ts +7/-0     
route.ts +15/-0   
route.ts +42/-0   
route.ts +5/-0     
route.ts +8/-0     
route.ts +13/-0   
layout.tsx +25/-0   
providers.tsx +13/-0   
prisma.ts +11/-0   
tsconfig.json +26/-0   

Co-authored-by: gglct.com <gglct.com@gmail.com>
@cursor
Copy link

cursor bot commented Aug 9, 2025

Cursor Agent can help with this pull request. Just @cursor in comments and I'll start working on changes in this branch.
Learn more about Cursor Agents

@Qomserver Qomserver marked this pull request as ready for review August 9, 2025 02:20
@qodo-code-review
Copy link

PR Reviewer Guide 🔍

Here are some key observations to aid the review process:

⏱️ Estimated effort to review: 4 🔵🔵🔵🔵⚪
🧪 No relevant tests
🔒 Security concerns

Sensitive information exposure:
The committed .env includes real-looking DB credentials and JWT secret placeholders. Even if for local use, these should be excluded via .gitignore and replaced with example values (e.g., .env.example) to prevent leakage.
JWT secret fallback: src/lib/auth.ts and middleware default to "secret" when JWT_SECRET is unset, weakening security if misconfigured. Fail closed or require the env var explicitly.
Cookie settings: Auth cookie uses sameSite: "lax" and secure only in production; consider sameSite: "strict" if feasible and set secure for all non-local environments.

⚡ Recommended focus areas for review

Sensitive Defaults

Hard-coded credentials and secrets are committed (e.g., DATABASE_URL with username/password and JWT_SECRET). These should be templated or provided via environment and avoided in source control.

DATABASE_URL="postgresql://postgres:postgres@localhost:5432/crm_suite?schema=public"
JWT_SECRET="change_this_secret"
NEXTAUTH_SECRET="unused"
APP_URL="http://localhost:3000"
Auth Bypass Risk

Public path matching uses startsWith, which may unintentionally allow access to unintended routes (e.g., paths prefixed with /login...). Consider exact matches or stricter route checks.

const publicPaths = ["/login", "/api/auth/login", "/api/auth/register", "/api/health"];

export async function middleware(request: NextRequest) {
  const { pathname } = request.nextUrl;
  if (publicPaths.some((p) => pathname.startsWith(p))) {
    const response = NextResponse.next();
    response.headers.set("Content-Language", "fa-IR");
    response.headers.set("Direction", "rtl");
    return response;
  }
N+1 Query

Computing conversion by source fetches each lead per order in a loop, causing N+1 DB queries. Prefer joining or preloading lead sources in one query to avoid performance issues.

const conversionBySource = await prisma.lead.groupBy({ by: ["source"], _count: { _all: true }, where: { source: { not: null } } });
const ordersBySource = await prisma.order.findMany({ select: { id: true, leadId: true, status: true }, where: { status: { in: ["paid", "completed"] } } });
const map = new Map<string, { leads: number; sales: number }>();
for (const g of conversionBySource) {
  const key = (g.source as string) || "نامشخص";
  map.set(key, { leads: g._count._all, sales: 0 });
}
for (const o of ordersBySource) {
  if (!o.leadId) continue;
  const lead = await prisma.lead.findUnique({ where: { id: o.leadId } });
  const key = lead?.source || "نامشخص";
  const entry = map.get(key) || { leads: 0, sales: 0 };
  entry.sales += 1;
  map.set(key, entry);
}
const bySource = Array.from(map.entries()).map(([source, v]) => ({ source, rate: v.leads ? v.sales / v.leads : 0 }));

@qodo-code-review
Copy link

qodo-code-review bot commented Aug 9, 2025

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
High-level
Add authz to all APIs

All core API routes (leads, orders, dashboard, etc.) are
unauthenticated/unauthorized beyond cookie presence in middleware and perform
unrestricted reads/writes directly against Prisma. Introduce server-side
authorization middleware/guards that decode JWT, load user/role/permissions, and
enforce RBAC per route (including field-level constraints like lead ownership)
to prevent data exfiltration and privilege escalation.

Examples:

crm-suite/src/app/api/leads/route.ts [4-11]
export async function GET() {
  const leads = await prisma.lead.findMany({
    orderBy: { createdAt: "desc" },
    include: { status: true, owner: true },
    take: 200,
  });
  return NextResponse.json(leads);
}
crm-suite/src/app/api/orders/route.ts [6-13]
export async function GET() {
  const orders = await prisma.order.findMany({
    include: { product: true, lead: true },
    orderBy: { createdAt: "desc" },
    take: 200,
  });
  return NextResponse.json(orders);
}

Solution Walkthrough:

Before:

// file: src/app/api/leads/route.ts
export async function GET() {
  // No authorization check. Any authenticated user can access all leads.
  const leads = await prisma.lead.findMany({
    orderBy: { createdAt: "desc" },
    include: { status: true, owner: true },
    take: 200,
  });
  return NextResponse.json(leads);
}

After:

// file: src/app/api/leads/route.ts
import { getCurrentUser } from "@/lib/session"; // hypothetical function

export async function GET() {
  const user = await getCurrentUser(); // Decodes JWT, fetches user/role
  if (!user || !user.hasPermission("read:leads")) {
    return new Response("Unauthorized", { status: 403 });
  }

  // Filter leads based on user's role/ownership
  const leads = await prisma.lead.findMany({
    where: { ownerId: user.id }, // Example: user can only see their own leads
    ...
  });
  return NextResponse.json(leads);
}
Suggestion importance[1-10]: 10

__

Why: This suggestion highlights a critical security vulnerability where API endpoints lack authorization, allowing any authenticated user to access all data, which undermines the RBAC system defined in the Prisma schema.

High
Security
Remove committed secrets

Do not commit real secrets into source control. Replace example secrets with
placeholders and instruct using environment-specific files or secrets manager to
avoid accidental exposure in deployments.

crm-suite/.env [7-10]

 DATABASE_URL="postgresql://postgres:postgres@localhost:5432/crm_suite?schema=public"
-JWT_SECRET="change_this_secret"
-NEXTAUTH_SECRET="unused"
+JWT_SECRET="__REPLACE_WITH_SECURE_RANDOM_SECRET__"
+NEXTAUTH_SECRET="__UNUSED__"
 APP_URL="http://localhost:3000"
  • Apply / Chat
Suggestion importance[1-10]: 9

__

Why: The suggestion correctly identifies that secrets are being committed to source control, which is a significant security risk, and proposes a valid improvement.

High
Disallow insecure JWT fallback

Using a weak default secret risks token forgery in non-configured environments.
Fail fast when JWT_SECRET is missing to prevent running with an insecure
fallback.

crm-suite/src/lib/auth.ts [4-5]

 const encoder = new TextEncoder();
-const secret = encoder.encode(process.env.JWT_SECRET || "secret");
+if (!process.env.JWT_SECRET) {
+  throw new Error("JWT_SECRET is not set");
+}
+const secret = encoder.encode(process.env.JWT_SECRET);
  • Apply / Chat
Suggestion importance[1-10]: 9

__

Why: The suggestion correctly identifies a critical security flaw where a weak default JWT_SECRET is used, and proposes failing fast to prevent insecure deployments.

High
Possible issue
Eliminate N+1 DB queries

Avoid N+1 queries inside the loop when mapping orders to sources. Fetch the
needed leads in a single query and build a lookup map to significantly reduce
database round trips and latency.

crm-suite/src/app/api/dashboard/route.ts [19-32]

-const ordersBySource = await prisma.order.findMany({ select: { id: true, leadId: true, status: true }, where: { status: { in: ["paid", "completed"] } } });
+const ordersBySource = await prisma.order.findMany({
+  select: { leadId: true, status: true },
+  where: { status: { in: ["paid", "completed"] } },
+});
 const map = new Map<string, { leads: number; sales: number }>();
 for (const g of conversionBySource) {
   const key = (g.source as string) || "نامشخص";
   map.set(key, { leads: g._count._all, sales: 0 });
 }
-for (const o of ordersBySource) {
-  if (!o.leadId) continue;
-  const lead = await prisma.lead.findUnique({ where: { id: o.leadId } });
-  const key = lead?.source || "نامشخص";
-  const entry = map.get(key) || { leads: 0, sales: 0 };
-  entry.sales += 1;
-  map.set(key, entry);
+const leadIds = Array.from(new Set(ordersBySource.map(o => o.leadId).filter(Boolean))) as string[];
+if (leadIds.length) {
+  const leads = await prisma.lead.findMany({ where: { id: { in: leadIds } }, select: { id: true, source: true } });
+  const leadMap = new Map(leads.map(l => [l.id, l.source || "نامشخص"]));
+  for (const o of ordersBySource) {
+    if (!o.leadId) continue;
+    const key = leadMap.get(o.leadId) || "نامشخص";
+    const entry = map.get(key) || { leads: 0, sales: 0 };
+    entry.sales += 1;
+    map.set(key, entry);
+  }
 }
  • Apply / Chat
Suggestion importance[1-10]: 8

__

Why: The suggestion correctly identifies a performance-critical N+1 query issue and provides an effective solution to optimize database access, which is a high-impact improvement.

Medium
  • Update

cursoragent and others added 3 commits August 9, 2025 02:37
Co-authored-by: gglct.com <gglct.com@gmail.com>
Co-authored-by: gglct.com <gglct.com@gmail.com>
@Qomserver Qomserver merged commit 8756f7a into main Aug 10, 2025
1 check failed
@Qomserver Qomserver changed the title Develop a full-featured sales crm system crm modiru.com Aug 10, 2025
Copy link
Owner Author

@Qomserver Qomserver left a comment

Choose a reason for hiding this comment

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

Crm.modiru com

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

Comments