Skip to main content

Project Structure

ForgeX uses a monorepo structure with independent microservices and shared packages.

Repository Overview

PSS-Bid-Manager/
├── services/ # Microservices
│ ├── portal/ # Portal (auth & app launcher)
│ ├── bids/ # Bids service (Phase 1)
│ ├── projects/ # Projects service (Phase 2)
│ └── field/ # Field service (Phase 3)
├── packages/ # Shared packages
│ └── shared/ # Common types & utilities
├── docs/ # Documentation
├── scripts/ # Automation scripts
├── docker-compose.yml # Local dev environment
└── deploy-to-production.sh # Deployment automation

Services Architecture

Each service follows the same structure:

services/<service-name>/
├── frontend/ # React + Vite SPA
│ ├── src/
│ │ ├── pages/ # Page components
│ │ ├── components/ # Reusable components
│ │ ├── hooks/ # Custom React hooks
│ │ ├── services/ # API client services
│ │ └── App.tsx # Main app component
│ ├── public/ # Static assets
│ ├── .env.example # Environment template
│ └── vite.config.ts # Vite configuration
├── backend/ # Node.js + Express API
│ ├── src/
│ │ ├── routes/ # API route handlers
│ │ ├── controllers/ # Business logic
│ │ ├── services/ # Service layer
│ │ ├── middleware/ # Express middleware
│ │ └── index.js # Server entry point
│ ├── prisma/ # Database schema & migrations
│ │ ├── schema.prisma
│ │ ├── migrations/
│ │ └── seed.js # Seed data
│ ├── .env.example # Environment template
│ └── Dockerfile # Container image
└── README.md # Service documentation
info

Consistent structure across all services makes it easy to onboard new developers and maintain code.

Service Details

Portal Service

Purpose: Authentication entry point and app launcher

URLs:

  • Local: http://localhost:3000
  • Production: https://forge.precisionsiteservices.com

Key Features:

  • Google OAuth authentication
  • Role-based app launcher
  • Session management
  • Subdomain navigation
services/portal/
└── frontend/
├── src/
│ ├── pages/
│ │ ├── Login.tsx # Google OAuth login
│ │ └── Dashboard.tsx # App launcher tiles
│ ├── components/
│ │ ├── AppTile.tsx # Service tile component
│ │ └── AuthGuard.tsx # Route protection
│ └── App.tsx
└── public/
└── images/ # Service logos

Bids Service (Phase 1 - Operational)

Purpose: Bid management and estimation

URLs:

  • Local: Frontend http://localhost:3001, API http://localhost:5001/api
  • Production: https://bids.precisionsiteservices.com

Database: bids_db (PostgreSQL)

Key Features:

  • Client & bid management
  • Estimation modules (Concrete, Labor, Equipment, Materials, Subcontractor)
  • Cost rollup & pricing
  • User management (shared across all services)
  • Admin panel
services/bids/
├── frontend/
│ └── src/
│ ├── pages/
│ │ ├── Dashboard.tsx # Bid list
│ │ ├── BidDetail.tsx # Bid detail & scopes
│ │ ├── admin/ # Admin pages
│ │ │ ├── UsersPage.tsx
│ │ │ ├── PricingPage.tsx
│ │ │ └── VariablesPage.tsx
│ │ └── ...
│ ├── hooks/ # Custom hooks
│ │ ├── useBids.ts
│ │ ├── useClients.ts
│ │ └── ...
│ └── services/
│ └── api.ts # API client
└── backend/
├── src/
│ ├── routes/
│ │ ├── authRoutes.js # Authentication
│ │ ├── bidRoutes.js # Bid CRUD
│ │ ├── clientRoutes.js # Client CRUD
│ │ └── ...
│ └── controllers/
│ ├── authController.js
│ ├── bidController.js
│ └── ...
└── prisma/
├── schema.prisma # Database schema
├── migrations/ # Schema migrations
└── seed.js # Initial data

Projects Service (Phase 2 - Coming Soon)

Purpose: Purchase orders and cost tracking

URLs:

  • Local: Frontend http://localhost:3002, API http://localhost:5002/api
  • Production: https://projects.precisionsiteservices.com

Database: projects_db (PostgreSQL)

Key Features:

  • Purchase order workflow
  • Vendor management
  • Receipt uploads (Cloud Storage)
  • Cost tracking (estimated vs actual)
  • Operations dashboard
  • Accounting approval workflow
services/projects/
├── frontend/
│ └── src/
│ └── pages/
│ ├── POList.tsx # Purchase order list
│ ├── PODetail.tsx # PO details & receipts
│ ├── VendorManager.tsx # Vendor management
│ └── OpsDashboard.tsx # Cost tracking dashboard
└── backend/
├── src/
│ ├── routes/
│ │ ├── poRoutes.js # PO CRUD
│ │ ├── vendorRoutes.js # Vendor CRUD
│ │ └── costRoutes.js # Cost tracking
│ └── services/
│ ├── bidsIntegration.js # Integrate with Bids service
│ └── gcsService.js # Cloud Storage for receipts
└── prisma/
└── schema.prisma # PO, Vendor, Receipt tables

Field Service (Phase 3 - Coming Soon)

Purpose: Timesheets and field operations

URLs:

  • Local: Frontend http://localhost:3003, API http://localhost:5003/api
  • Production: https://field.precisionsiteservices.com

Database: field_db (PostgreSQL)

Key Features:

  • Employee management (with PIN authentication)
  • Timesheet entry (GPS clock-in/out)
  • Foreman dashboard
  • Crew management
  • Daily reports (Cloud Storage)
services/field/
├── frontend/
│ └── src/
│ └── pages/
│ ├── ClockIn.tsx # GPS clock-in/out
│ ├── Timesheets.tsx # Timesheet history
│ ├── ForemanDashboard.tsx # Crew management
│ └── EmployeeManager.tsx # Employee CRUD
└── backend/
├── src/
│ ├── routes/
│ │ ├── timesheetRoutes.js # Timesheet CRUD
│ │ ├── employeeRoutes.js # Employee CRUD
│ │ └── gpsRoutes.js # GPS validation
│ └── services/
│ ├── projectsIntegration.js # Integrate with Projects service
│ └── gpsService.js # GPS geofencing
└── prisma/
└── schema.prisma # Employee, Timesheet tables

Shared Package

The packages/shared/ directory contains code used across multiple services:

packages/shared/
├── constants/
│ ├── features.js # Feature flags
│ └── roles.js # User role definitions
├── types/
│ └── common.ts # TypeScript types
└── utils/
├── validation.js # Input validation
└── formatting.js # Data formatting
📦

Shared Package Guide

Learn how to use and extend the shared package

Key Files

🐳docker-compose.yml

Purpose: Local development environment

Defines:

  • 3 PostgreSQL databases (bids, projects, field)
  • 7 services (portal, bids, projects, field frontends + backends)
  • 10 total containers
  • Port mappings and volumes
services:
# Databases
bids-db:
image: postgres:15
ports: ["5432:5432"]

# Bids Service
bids-backend:
build: ./services/bids/backend
ports: ["5001:5000"]
depends_on: [bids-db]

bids-frontend:
build: ./services/bids/frontend
ports: ["3001:3001"]
🗄️prisma/schema.prisma

Purpose: Database schema definition

Each service has its own:

  • services/bids/backend/prisma/schema.prisma
  • services/projects/backend/prisma/schema.prisma
  • services/field/backend/prisma/schema.prisma

Example (Bids):

model User {
id String @id @default(uuid())
email String @unique
name String?
role UserRole
createdAt DateTime @default(now())
bids Bid[]
}

model Bid {
id String @id @default(uuid())
name String
clientId String
client Client @relation(fields: [clientId], references: [id])
scopes Scope[]
createdById String
createdBy User @relation(fields: [createdById], references: [id])
}
🔑.env.example

Purpose: Environment variable template

Location: Each service has frontend/.env.example and backend/.env.example

Usage:

cp services/bids/backend/.env.example services/bids/backend/.env
# Edit .env with actual values
🔑

Environment Variables

Complete environment variable reference

🔨cloudbuild.yaml

Purpose: Google Cloud Build configuration

Location: services/<service>/backend/cloudbuild.yaml

Builds Docker image for Cloud Run deployment

steps:
- name: 'gcr.io/cloud-builders/docker'
args: ['build', '-t', 'gcr.io/forge-475221/bids-backend:latest', '.']
images:
- 'gcr.io/forge-475221/bids-backend:latest'
🚀deploy-to-production.sh

Purpose: Automated deployment script

Does:

  1. Builds portal and bids frontends
  2. Uploads to Cloud Storage
  3. Builds backend Docker image
  4. Deploys to Cloud Run
  5. Invalidates CDN cache
./deploy-to-production.sh

Package Dependencies

Frontend Stack

{
"dependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router-dom": "^6.20.0",
"@mui/material": "^5.14.0",
"@mui/icons-material": "^5.14.0",
"axios": "^1.6.0",
"@tanstack/react-query": "^5.0.0"
},
"devDependencies": {
"@vitejs/plugin-react": "^4.2.0",
"vite": "^5.0.0",
"typescript": "^5.3.0"
}
}

Backend Stack

{
"dependencies": {
"express": "^4.18.0",
"prisma": "^5.7.0",
"@prisma/client": "^5.7.0",
"jsonwebtoken": "^9.0.0",
"bcryptjs": "^2.4.3",
"cors": "^2.8.5",
"helmet": "^7.1.0",
"@google-cloud/storage": "^7.7.0",
"winston": "^3.11.0"
},
"devDependencies": {
"nodemon": "^3.0.0",
"jest": "^29.7.0"
}
}

Development Workflow

1
Clone Repository
git clone https://github.com/your-org/PSS-Bid-Manager.git
cd PSS-Bid-Manager
2
Setup Environment Files
# Copy .env.example files
cp services/bids/backend/.env.example services/bids/backend/.env
cp services/bids/frontend/.env.example services/bids/frontend/.env

# Edit with your values
nano services/bids/backend/.env
3
Start Docker Compose
docker-compose up -d
4
Run Migrations & Seed
# Bids database
docker-compose exec bids-backend npx prisma migrate dev
docker-compose exec bids-backend npx prisma db seed
🐳

Docker Compose Guide

Complete local development environment setup

Code Style

Backend (JavaScript)

// Use async/await, not callbacks
app.get('/api/bids/:id', async (req, res) => {
try {
const bid = await db.bid.findUnique({ where: { id: req.params.id } });
res.json(bid);
} catch (error) {
res.status(500).json({ error: error.message });
}
});

// Use Prisma for database queries
const bids = await db.bid.findMany({
where: { clientId },
include: { client: true, scopes: true }
});

// Use middleware for auth
app.use('/api/bids', requireAuth, bidRoutes);

Frontend (TypeScript + React)

// Use functional components with hooks
export const BidList: React.FC = () => {
const { data: bids, isLoading } = useBids();

if (isLoading) return <CircularProgress />;

return (
<Box>
{bids?.map(bid => (
<BidCard key={bid.id} bid={bid} />
))}
</Box>
);
};

// Use React Query for API calls
export const useBids = () => {
return useQuery({
queryKey: ['bids'],
queryFn: () => api.get('/bids').then(res => res.data)
});
};

Testing

cd services/bids/backend
npm test

# Run specific test
npm test -- bidController.test.js

# Coverage
npm test -- --coverage

Example test:

describe('GET /api/bids', () => {
it('returns bids for authenticated user', async () => {
const res = await request(app)
.get('/api/bids')
.set('Cookie', `sAccessToken=${mockSessionToken}`);

expect(res.status).toBe(200);
expect(res.body).toBeInstanceOf(Array);
});
});

Next Steps