Job hunting turns into a spreadsheet of companies, dates, and “should I follow up yet?” JobTrack replaces the spreadsheet with a board where each application moves through Saved, Applied, Interview, Offer, Rejected, and Withdrawn, and a background job that spots applications that have gone quiet for seven days. It is a small product, and that is the point: small enough to read in an afternoon, complete enough to show how the pieces fit.
What a reviewer can look at
If you are skimming the repo, these are the places where the decisions are visible:
- Data ownership in the query. Every read is filtered by the signed-in user, and another user's record ID returns 404, not 403. The boundary lives in the persistence layer, not in the UI.
- An API with real contracts. Pagination with a capped page size, search, status filters, allow-listed sort fields, and Problem Details for validation errors. Invalid sort fields get a 400, not a silent default.
- Tests through the real HTTP pipeline. xUnit controller tests plus
WebApplicationFactoryintegration tests covering routing, serialization, validation, and user scoping against an isolated in-memory database, with Angular tests in the same CI run. - An honest background job. The Functions app runs on a five-minute timer and logs applications still marked Applied after seven days. The reminder design study explains why turning that log line into an email needs deduplication and retry rules first.
- A pipeline that stops short on purpose. Azure Pipelines builds, tests, and deploys the API. Database migrations stay a deliberate manual step.
The application
The Angular 22 client uses standalone components, signals, and reactive forms. It has route guards and an HTTP interceptor that attaches the token. You can register, sign in, and then create, edit, move, search, filter, sort, and delete applications in either a board or a list view.
Tokens are kept in memory only. The trade-off is a sign-in after every refresh, which I accepted because a token in local storage is readable by any script that runs on the page. A refresh-token flow is the natural next step, and it is not built yet.
The API and data
The API is ASP.NET Core 10 with controllers, ASP.NET Core Identity for accounts, and signed JWTs. EF Core 10 maps to SQL Server through checked-in migrations. The model enforces required fields and length limits, stores salary as a decimal, saves dates in UTC, and normalizes status values so interview and Interview mean the same thing. Reads use no-tracking queries where change tracking would only add overhead.
One migration adds identity and ownership, and it leaves earlier rows with no owner. The API hides them from every user, and the README explains how to assign them to an account before upgrading an existing database.
Running it
Copy the example env file and run docker compose up --build. That starts Angular, the API, and SQL Server with a health check, and the schema is created automatically. Each project still runs on its own if you want to work on one layer.
Known gaps
I listed these in the README, and they are the work between here and production:
- CORS allows only localhost origins, and Swagger is on in every environment.
- Access tokens expire without a refresh flow.
- The pipeline does not run migrations, and the Functions app and web client have build artifacts but no deployment stage.
- The reminder function logs candidates but does not notify anyone.