I wanted a portfolio project that goes beyond CRUD — something with real multi-tenancy, role-based permissions, payment integration, and deployment. So I'm building TracKeee, an invoicing app tailored for South African freelancers and small agencies.
It handles SA VAT (15%), ZAR currency, integrates with Yoco for payments, and complies with POPIA. Still a work in progress, but I've learned enough to share.
The stack
- Backend: ASP.NET Core MVC (.NET 8)
- Database: Azure SQL + Entity Framework Core
- Auth: ASP.NET Core Identity
- PDFs: QuestPDF
- Email: Brevo SMTP
- Payments: Yoco Checkout API
- Charts: Chart.js
- Hosting: Azure App Service (South Africa North)
The biggest lesson: plan for teams from day one
I started by building everything around a single user. Each user had their own clients, projects, invoices. Simple and it worked.
Then I realised a small agency needs multiple people sharing the same workspace — an owner, an accountant, a project manager. So I refactored the entire data model from user-based to organisation-based. Every model, every controller, every query changed.
It worked, but it would've been way easier if I'd designed for it from the start.
Centralised permissions saved me hours
I have five roles: Owner, Admin, Manager, Accountant, Employee. Instead of checking roles everywhere, I built one method that controls everything:
public bool HasPermission(OrganizationRole role, string action)
{
return action switch
{
"ManageClients" => role == OrganizationRole.Owner
|| role == OrganizationRole.Admin
|| role == OrganizationRole.Manager,
"ViewFinancials" => role == OrganizationRole.Owner
|| role == OrganizationRole.Admin
|| role == OrganizationRole.Accountant,
"Delete" => role == OrganizationRole.Owner,
_ => false
};
}
Every controller and view calls this one method. Adding a new role means updating one file.
I initially used shortcuts like role != Employee but switched to explicit positive checks — because adding a new role would silently inherit all those permissions. Small thing, but the kind of mistake that causes real security issues.
What's working so far
- Clients, projects, time entries with organisation-level isolation
- Live start/stop timer in the navbar
- Invoice generation from uninvoiced time entries with 15% VAT
- Branded PDF invoices with business profile and payment links
- Email invoices directly to clients with PDF attached
- Yoco payment integration — each freelancer uses their own account
- Dashboard with charts (hours by month, revenue, hours by client)
- Reports page with date range filtering
- Search and filtering on all list pages
- Five team roles with permission-based UI
- POPIA-compliant legal pages and cookie consent
- Account lockout and session security
What's next
- Client portal
- CSV/Excel exports
- Activity log
- Replacing Bootstrap with a custom UI
If you've built multi-tenant SaaS with .NET, I'd love to hear how you handled permissions and tenant isolation.
Top comments (0)