| Before | After |
|---|---|
| Supabase PostgreSQL | SQL Server Express |
| Supabase Auth (JWT) | ASP.NET Core JWT + Refresh Tokens |
| Supabase Realtime | SignalR (WebSockets) |
| Supabase Edge Functions | ASP.NET Core Controllers |
| Row-Level Security | Role-based Authorization Policies |
| Direct DB queries from frontend | REST API via src/lib/apiClient.ts |
| Tool | Version | Link |
|---|---|---|
| .NET SDK | 8.0+ | https://dotnet.microsoft.com/download |
| SQL Server Express | 2019+ | https://www.microsoft.com/en-us/sql-server/sql-server-downloads |
| VS Code | Latest | https://code.visualstudio.com |
| C# Dev Kit Extension | Latest | ms-dotnettools.csdevkit in VS Code |
| Node.js | 18+ | https://nodejs.org |
winget install Microsoft.DotNet.SDK.8
# or download from https://dotnet.microsoft.com/download/dotnet/8.0
dotnet --version # should show 8.x.x
Verify it's running:
# Check service
sc query "MSSQL$SQLEXPRESS"
# Start if stopped
net start "MSSQL$SQLEXPRESS"
dotnet tool install --global dotnet-ef
# Verify
dotnet ef --versioncd backend/BuildTrack.API
dotnet restore
dotnet build# Option A: Let auto-migration create it (Development mode)
dotnet run # Migration runs automatically on startup in dev
# Option B: Manual
sqlcmd -S "localhost\SQLEXPRESS" -E -Q "CREATE DATABASE BuildTrackDB"
dotnet ef migrations add InitialCreate --output-dir Data/Migrations
dotnet ef database updatesqlcmd -S "localhost\SQLEXPRESS" -d BuildTrackDB -E -i ..\..\database-scripts\002_seed_admin.sqlDefault login: admin@buildtrack.com / Admin@123456
Change this password immediately after first login!
sqlcmd -S "localhost\SQLEXPRESS" -d BuildTrackDB -E -i ..\..\database-scripts\003_seed_sample_data.sqlcd backend/BuildTrack.API
dotnet run- API:
http://localhost:5069 - Swagger:
http://localhost:5069/swagger
npm install @microsoft/signalrnpm uninstall @supabase/supabase-js
# Note: Keep it installed if pages still reference it during migrationVITE_API_BASE_URL=http://localhost:5069
npm run devFrontend: http://localhost:8080
Frontend (React + TypeScript)
│
├─ src/lib/apiClient.ts ← All API calls, JWT token management
├─ src/contexts/AuthContext.tsx ← JWT auth state
├─ src/hooks/useNotifications.ts ← REST polling + SignalR
├─ src/lib/activityLogger.ts ← → /api/audit-logs
└─ src/lib/notificationService.ts ← → /api/notifications
│
│ HTTP + SignalR WebSocket
▼
Backend (ASP.NET Core 8) http://localhost:5069
│
├─ Controllers/ ← REST endpoints
├─ Services/ ← Business logic
├─ Data/ ← EF Core AppDbContext
├─ Models/Entities/ ← C# entity classes (map to DB tables)
├─ DTOs/ ← Request/Response shapes
├─ Hubs/ ← SignalR NotificationHub (/hubs/notifications)
└─ Middleware/ ← Global error handling
│
│ Entity Framework Core
▼
SQL Server Express (BuildTrackDB) localhost\SQLEXPRESS
POST /api/auth/login { loginId, password }
POST /api/auth/refresh { refreshToken }
POST /api/auth/logout
GET /api/auth/me
POST /api/auth/change-password { currentPassword, newPassword }
GET /api/users
GET /api/users/{id}
GET /api/users/me
POST /api/users [admin]
PUT /api/users/{id}
DELETE /api/users/{id} [admin]
GET /api/users/{id}/roles
POST /api/users/{id}/roles [admin] { role }
DELETE /api/users/{id}/roles/{role} [admin]
GET /api/projects?includeHidden&status&search
GET /api/projects/{id}
POST /api/projects
PUT /api/projects/{id}
DELETE /api/projects/{id} [admin]
GET /api/projects/{id}/members
POST /api/projects/{id}/members { userId, role }
DELETE /api/projects/{id}/members/{userId}
GET /api/projects/{id}/progress
GET /api/projects/{id}/activity?limit
GET /api/orders?status&projectId&search
GET /api/orders/{id}
GET /api/orders/project/{projectId}?status
POST /api/orders { projectId, orderType, supplierName, items[] }
PUT /api/orders/{id}
DELETE /api/orders/{id}
POST /api/orders/{id}/submit
POST /api/orders/{id}/approve [approver] { notes }
POST /api/orders/{id}/reject [approver] { reason }
POST /api/orders/{id}/status { status, notes }
GET /api/skus?search&isActive&category&sortBy&sortOrder
GET /api/skus/{id}
POST /api/skus [warehouse_admin]
PUT /api/skus/{id} [warehouse_admin]
DELETE /api/skus/{id} [warehouse_admin]
GET /api/inventory?projectId
GET /api/inventory/project/{projectId}
GET /api/inventory/transactions?projectId&skuId&limit
POST /api/inventory/transactions { projectId, skuId, transactionType, quantity }
GET /api/company-assets?search&assetType
GET /api/company-assets/{id}
POST /api/company-assets [warehouse_admin]
PUT /api/company-assets/{id} [warehouse_admin]
DELETE /api/company-assets/{id} [warehouse_admin]
GET /api/company-assets/{id}/borrows
GET /api/company-assets/borrow-transactions?projectId
POST /api/company-assets/borrow { assetId, projectId, quantity, expectedReturnDate }
POST /api/company-assets/borrow-transactions/{id}/return { returnedQty, remarks }
GET /api/quotations?projectId
GET /api/quotations/{id}
POST /api/quotations { projectId, notes, category, items[] }
DELETE /api/quotations/{id} [office_admin]
PUT /api/quotations/{id}/items/{itemId} { materialName, unit, quantity }
GET /api/quotations/change-requests?projectId&status
POST /api/quotations/change-requests { projectId, quotationId, changeType, payload }
PUT /api/quotations/change-requests/{id} [office_admin] { status, reviewRemarks }
GET /api/notifications?unreadOnly
PUT /api/notifications/{id}/read
PUT /api/notifications/read-all
DELETE /api/notifications/{id}
GET /api/dashboard/stats
GET /api/audit-logs?tableName&userId&limit [admin]
POST /api/audit-logs
WebSocket: /hubs/notifications
Events pushed to client: NewNotification
Find pages still using Supabase directly:
grep -r "supabase\." src/pages/ src/components/ --include="*.tsx" -lPattern for migrating a page:
// BEFORE
import { supabase } from '@/integrations/supabase/client';
const { data, error } = await supabase.from('orders').select('*');
// AFTER
import { ordersApi } from '@/lib/apiClient';
const res = await ordersApi.getAll();
const data = res.data ?? [];Key API mappings:
| Old Supabase | New API |
|---|---|
supabase.from('orders').select(...) |
ordersApi.getAll(...) |
supabase.from('projects').select(...) |
projectsApi.getAll(...) |
supabase.from('skus').select(...) |
skusApi.getAll(...) |
supabase.from('profiles').select(...) |
usersApi.getAll() |
supabase.from('user_roles').select(...) |
usersApi.getRoles(id) |
supabase.from('notifications').select(...) |
notificationsApi.getAll() |
supabase.from('company_assets').select(...) |
companyAssetsApi.getAll() |
supabase.from('borrow_transactions').select(...) |
companyAssetsApi.getAllBorrows() |
supabase.from('project_quotations').select(...) |
quotationsApi.getAll(projectId) |
supabase.from('quotation_change_requests').select(...) |
quotationsApi.getChangeRequests() |
supabase.from('project_inventory').select(...) |
inventoryApi.getAll(projectId) |
supabase.rpc('create_project_with_membership', ...) |
projectsApi.create(...) |
| Role | Policy | Capabilities |
|---|---|---|
super_admin, admin |
RequireAdmin |
Full access |
office_admin |
RequireOfficeAdmin |
Approve orders, change requests |
approver, approval_admin |
RequireApprover |
Approve/reject orders |
warehouse_admin |
RequireWarehouseAdmin |
Manage SKUs, assets, inventory |
project_manager, project_engineer |
RequireProjectManager |
Create projects/orders |
logistics_admin, driver, tracking_driver |
RequireLogistics |
Logistics operations |
receiver, storekeeper |
RequireReceiver |
Receive deliveries |
- Start SQL Server:
net start "MSSQL$SQLEXPRESS" - Check instance name in
appsettings.Development.json - Try connecting with SSMS first
# Remove last migration and recreate
dotnet ef migrations remove
dotnet ef migrations add InitialCreate
dotnet ef database updateAdd your frontend URL to appsettings.json:
"Cors": { "AllowedOrigins": ["http://localhost:8080"] }- Check
VITE_API_BASE_URLin.envmatches the API port - Verify JWT secret is the same in
appsettings.Development.json - Clear localStorage and log in again
npm install @microsoft/signalrFalls back to 30s polling if not installed.
Generate a proper secret:
node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"