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
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, APIhttp://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, APIhttp://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, APIhttp://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.prismaservices/projects/backend/prisma/schema.prismaservices/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:
- Builds portal and bids frontends
- Uploads to Cloud Storage
- Builds backend Docker image
- Deploys to Cloud Run
- 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
git clone https://github.com/your-org/PSS-Bid-Manager.git
cd PSS-Bid-Manager
# 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
docker-compose up -d
# Bids database
docker-compose exec bids-backend npx prisma migrate dev
docker-compose exec bids-backend npx prisma db seed
- Portal: http://localhost:3000
- Bids: http://localhost:3001
- Bids API: http://localhost:5001/api/health
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
- Backend Tests
- Frontend Tests
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);
});
});
cd services/bids/frontend
npm test
# Run specific test
npm test -- BidList.test.tsx
Example test:
test('renders bid list', () => {
render(<BidList />);
expect(screen.getByText('Bids')).toBeInTheDocument();
});