Skip to content

VerdantForge/JobTracker

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

JobTracker

Context

This software's objective is to serve as a collaborative platform for job applications with an API first design. This means it should be easy for people to extend it and integrate in to much broader workflows. I built this first of all so that I could collaborate with AI agents in my Job Application pipeline thus offering a playground for hybrid AI + Human work.

Data Model

This section defines the conceptual model and core entities. Design goals: multi‑tenant collaboration, AI + human augmentation, clear provenance, and extensibility for ingestion pipelines.

Tenancy & Visibility

Two visibility classes:

  1. Public (globally visible): Ingested Company and public JobPosting records.
  2. Organization‑scoped: User‑created JobPostings, Applications, Conversations, Attachments, and internal enrichment artifacts. Every org‑scoped entity carries OrganizationId.

Users may belong to multiple organizations via membership records. Authorization will typically enforce that a user can only mutate entities within orgs they are a member of (except public read). Future: row‑level policies / ABAC.

Core Entities

JobPosting

Represents an externally advertised role or an internally tracked opportunity. Fields:

  • Id (PK)
  • OrganizationId (nullable for public / ingested postings)
  • CompanyId (FK Company)
  • Title
  • Description (Markdown, with optional LanguageCode)
  • ApplicationProcedure: { Link (URL), Type (enum ApplicationType) }
  • Location (freeform now; future: structured geolocation)
  • WorkloadFte (decimal 0–1; derived from “Workload”)
  • ContractType (enum)
  • SpokenLanguageRequirements [LanguageRequirement]
  • PublishedAt (UTC)
  • SalaryRange { Min, Max, Currency (ISO 4217), Period = Annual }
  • ExternalIds [ExternalId]
  • CreatedAt / UpdatedAt
  • ArchivedAt (nullable)

Relationships:

  • Company (M:1)
  • Applications (1:M)

Company

Fields:

  • Id
  • Name
  • Description
  • Location (freeform)
  • EmployeeCount (nullable)
  • ExternalIds [ExternalId]
  • CreatedAt / UpdatedAt
  • ArchivedAt

Relationships:

  • JobPostings (1:M)
  • Contacts (1:M)

Contact

Represents a person associated with a single Company (simplified; no historical moves yet). Fields: Id, CompanyId, FirstName, LastName, Email, Phone, RoleTitle, CreatedAt, UpdatedAt, ArchivedAt.

User

Can be Human or AI agent. Fields: Id, DisplayName, Type (Human|AI), CreatedAt, UpdatedAt, Active, ArchivedAt.

Organization

Fields: Id, Name, CreatedAt, ArchivedAt.

UserOrganization (Membership)

Junction for many‑to‑many membership. Fields: UserId, OrganizationId, Role (Owner|Member|Automation), JoinedAt, ArchivedAt.

Application

Represents one submission attempt for a JobPosting by a User. Multiple Applications per JobPosting & User are allowed (e.g., spontaneous + tailored follow‑up). Includes assignable ownership within the org for workflow. Fields:

  • Id
  • OrganizationId (FK)
  • JobPostingId (FK)
  • ApplicantUserId (FK User)
  • Title (e.g., “Spontaneous – May 2025”)
  • Status (enum ApplicationStatus)
  • StatusChangedAt
  • AssignedToUserId (nullable; current handler – AI or human)
  • SubmittedAt (nullable until actually sent)
  • ExternalIds [ExternalId]
  • CreatedAt / UpdatedAt / ArchivedAt

Relationships:

  • JobPosting (M:1)
  • ApplicantUser (M:1 User)
  • Questions (1:M ApplicationQuestion)
  • Attachments (M:N via AttachmentLink)
  • StatusHistory (1:M ApplicationStatusEvent)
  • Conversations (1:M Conversation)

ApplicationQuestion

Stores structured Q&A prompts. Fields: Id, ApplicationId, Prompt, Answer, Source (Form|Manual|AI), OrderIndex, CreatedAt, UpdatedAt.

Attachment

Generic file / document reference. Fields: Id, OrganizationId (nullable for public), Uri, Type (enum AttachmentType), MimeType, Hash (integrity), CreatedAt, UploadedByUserId (nullable if ingested), GeneratedBy (Human|AI), ArchivedAt.

AttachmentLink

Associates Attachments to parent entities (e.g., Application, JobPosting) without duplicating rows. Fields: Id, AttachmentId, EntityType, EntityId, CreatedAt.

Conversation

Unified channel for communications & interviews (email threads, chat, phone notes, in‑person interview transcripts). Fields:

  • Id
  • OrganizationId
  • ApplicationId (FK)
  • Type (enum ConversationType) e.g., General, Interview, OfferNegotiation
  • Channel (enum Channel) e.g., Email, LinkedIn, PhoneCall, InPerson, Chat
  • StartedAt, ClosedAt (nullable)
  • CreatedAt / UpdatedAt / ArchivedAt

Message

Fields: Id, ConversationId, FromUserId (nullable), FromContactId (nullable), SentAt, Content (Markdown/plain), GeneratedBy (Human|AI), ConfidenceScore (nullable), Provenance (JSON), CreatedAt.

ApplicationStatusEvent

Immutable audit of status transitions. Fields: Id, ApplicationId, FromStatus, ToStatus, ChangedAt, ChangedByUserId (nullable if AI), Reason (nullable), Metadata (JSON).

EventLog

Generic audit events beyond status: Created, Updated, Archived, AttachmentAdded, AssignmentChanged, AIActionPerformed. Fields: Id, EntityType, EntityId, EventType, ActorUserId (nullable), OccurredAt, Metadata (JSON), CorrelationId.

InterviewContext

Represents a container for interview questions and answers, providing context about the interview scenario. Used by AI agents (like CandidateRepresenter.Bot) to access a knowledge base of pre-answered questions. Fields:

  • Id
  • OrganizationId (FK Organization) - organization-level scoping for shared Q&A banks
  • JobPostingId (nullable FK) - optional link to specific job posting for context
  • CompanyId (nullable FK) - optional link to company for context
  • JobApplicationId (nullable FK) - optional link to actual application (enables tracking bot-generated answers)
  • InterviewType (enum) - Simulated or Real
  • Notes (optional)
  • CreatedAt / UpdatedAt / ArchivedAt

Relationships:

  • Questions (1:M InterviewQuestion)
  • JobPosting (M:1, optional)
  • Company (M:1, optional)
  • JobApplication (M:1, optional)

InterviewQuestion

Individual question-answer pairs within an InterviewContext. Tracks provenance and confidence for AI-generated content. Fields:

  • Id
  • InterviewContextId (FK)
  • Question (max 1000 chars)
  • Answer (max 4000 chars)
  • GeneratedBy (enum) - Human or AI
  • ConfidenceScore (nullable decimal 0-1) - for AI-generated answers
  • Notes (optional)
  • CreatedAt / UpdatedAt / ArchivedAt

Methods:

  • IsAIGenerated() - convenience method to check if AI-generated
  • HasHighConfidence() - returns true if confidence score >= 0.8

Structured / Value Types

ExternalId { System, Value } LanguageRequirement { LanguageCode (ISO 639‑1), Level (enum LanguageLevel) } SalaryRange { Min, Max, Currency, Period }

Enumerations

  • ApplicationType: LinkedInEasyApply, ExternalPortal, InternalATS, GenericForm
  • ContractType: Permanent, Internship, Contractor, FixedTerm
  • ApplicationStatus: Draft, Preparing, Submitted, Screening, Interviewing, Offer, Hired, Rejected, Withdrawn
  • ConversationType: General, Interview, OfferNegotiation, FollowUp
  • Channel: Email, LinkedIn, PhoneCall, InPerson, Chat, System
  • AttachmentType: Resume, CoverLetter, Portfolio, Transcript, Other
  • LanguageLevel: Basic, Conversational, Professional, Native
  • GeneratedBy: Human, AI
  • InterviewType: Simulated, Real

Relationships (Cardinality Overview)

Company (1) ── (M) JobPosting Company (1) ── (M) Contact JobPosting (1) ── (M) Application Application (1) ── (M) ApplicationQuestion Application (1) ── (M) Conversation Conversation (1) ── (M) Message Application (1) ── (M) ApplicationStatusEvent User (M) ── (M) Organization (via UserOrganization) Entity (1) ── (M) EventLog Attachment (M) ── (M) (Entities) via AttachmentLink InterviewContext (1) ── (M) InterviewQuestion InterviewContext (M) ── (1) Organization InterviewContext (M) ── (1) JobPosting (optional) InterviewContext (M) ── (1) Company (optional) InterviewContext (M) ── (1) JobApplication (optional)

Lifecycle & Auditing

All mutable entities include CreatedAt, UpdatedAt, and optional ArchivedAt for soft deletion. State transitions for Applications are immutable via ApplicationStatusEvent. EventLog provides extensible auditing and correlation for multi‑step AI + human workflows. AI provenance captured through GeneratedBy + optional ConfidenceScore + Metadata.

AI & Automation Considerations

  • Messages, Attachments, Answers can be AI generated; provenance stored.
  • Assignment model supports routing: initial AI drafting then human submission.
  • Future: scoring agents can enrich Applications (stored as separate enrichment events rather than mutating base rows).

Performance & Scaling Notes (Early Thoughts)

  • High write volume expected in Messages & EventLog → index (ConversationId, SentAt) and (EntityType, EntityId, OccurredAt).
  • Attachments stored out‑of‑band (object storage) addressed by immutable Uri + Hash.
  • Status transitions append‑only → suitable for event sourcing of Application aggregate.

Deferred / Future Considerations

  • Privacy & PII handling (encryption at rest for Contact + Message content if required).
  • Consent / lawful basis tracking for Contact data.
  • Company & JobPosting deduplication handled upstream by ingestion pipeline (not in this service).
  • Localization of descriptions; fallback strategy.
  • Search indexing model (e.g., projection tables or external search service).
  • Role‑based access control granularity beyond membership Role.

Rationale Summary

The model favors normalization for auditability (status events, messages) while keeping value objects (language requirements, external IDs) inline for developer ergonomics. Conversations unify communications and interviews, reducing surface area. Public vs org‑scoped split enables shared ingestion while preserving collaborative privacy.

Development Setup

Prerequisites

  • .NET 8.0 SDK

Running the Application

  1. Clone the repository
  2. Navigate to the project directory
  3. Run dotnet restore to restore dependencies
  4. Run dotnet run --project JobTracker to start the API
  5. The API will be available at https://localhost:7226 (HTTPS) or http://localhost:5138 (HTTP)

API Documentation

The project uses Swashbuckle for OpenAPI documentation generation and SwaggerUI hosting:

  • Swagger UI: Available at /swagger when running in Development mode
  • OpenAPI JSON: Available at /swagger/v1/swagger.json
  • Runtime Generation: OpenAPI specification is automatically generated at runtime

Features

  • Interactive API documentation with SwaggerUI
  • Automatic OpenAPI 3.0 specification generation
  • Clean and reliable documentation generation
  • Comprehensive endpoint documentation with request/response schemas
  • Try-it-out functionality for testing endpoints directly from the UI

Accessing Documentation

When running the application in Development mode:

  1. Start the application: dotnet run --project JobTracker
  2. Open your browser to: http://localhost:5138/swagger
  3. Explore and test the API endpoints interactively

Debugging in Visual Studio Code

The project now includes comprehensive debugging support with the following configurations:

Available Debug Configurations

  1. Launch JobTracker API - Starts the API with automatic browser opening
  2. Launch JobTracker API (No Browser) - Starts the API without opening browser
  3. Attach to JobTracker - Attaches debugger to running process
  4. Debug Tests - Runs unit tests with debugging support

Key Features

  • Full breakpoint support with IntelliSense
  • Inline variable inspection
  • Debug console with auto-completion
  • Automatic build before debugging
  • Environment variable configuration
  • Test debugging capabilities

Quick Start Debugging

  1. Open the project in VS Code
  2. Press F5 or go to Run and Debug panel
  3. Select "Launch JobTracker API" configuration
  4. Set breakpoints in your code
  5. The API will start and browser will open automatically

Development Tasks

Available via Command Palette (Ctrl+Shift+P) → "Tasks: Run Task":

  • build - Build the project
  • test - Run all tests
  • watch - Run with file watching
  • clean - Clean build artifacts
  • restore - Restore NuGet packages

Running Tests

# Run all tests
dotnet test

# Run tests with detailed output
dotnet test --logger "console;verbosity=detailed"

# Run tests in specific project
dotnet test tests/JobTracker.Tests/JobTracker.Tests.csproj

Dependency Management

The project uses dotnet-outdated to keep dependencies current and maintain security. This tool helps identify and update NuGet packages to their latest versions.

Installation

# Install the global tool (one-time setup)
dotnet tool install --global dotnet-outdated-tool

Updating the Tool

# Keep the tool itself updated
dotnet tool update --global dotnet-outdated-tool

Usage

# Check for outdated packages and update them automatically
dotnet outdated -u

# Just check without updating (dry run)
dotnet outdated

# Update packages in specific project
dotnet outdated -u --project JobTracker/JobTracker.csproj

# Update test project dependencies
dotnet outdated -u --project tests/JobTracker.Tests/JobTracker.Tests.csproj

Best Practices

  • Run dotnet outdated -u regularly to keep dependencies current
  • Always test thoroughly after dependency updates
  • Review breaking changes for major version updates
  • Consider running updates on a separate branch for larger projects

Database Migrations

The project uses Entity Framework Core for data persistence. When making changes to the database schema (entities, configurations, etc.), you must create and apply migrations.

Creating Migrations

# Create a new migration after modifying entities or configurations
dotnet ef migrations add <MigrationName> --project JobTracker

# Examples:
dotnet ef migrations add AddApplicationStatusEvents --project JobTracker
dotnet ef migrations add UpdateUserEntity --project JobTracker
dotnet ef migrations add AddConversationSupport --project JobTracker

Applying Migrations

# Apply pending migrations to the database
dotnet ef database update --project JobTracker

# Apply migrations up to a specific migration
dotnet ef database update <MigrationName> --project JobTracker

Migration Best Practices

  • Always create a migration when you modify:
    • Entity classes in Domain/Entities/
    • Entity configurations in Persistence/Configurations/
    • DbContext or DbSet declarations
  • Use descriptive migration names that clearly indicate what changed
  • Review the generated migration code before applying it
  • Test migrations on a copy of production data before deploying
  • Never modify existing migration files; create new ones for changes
  • Consider data migration scripts for complex schema changes that affect existing data

Useful Migration Commands

# Remove the last migration (if not yet applied to database)
dotnet ef migrations remove --project JobTracker

# List all migrations and their status
dotnet ef migrations list --project JobTracker

# Generate SQL script for migrations
dotnet ef migrations script --project JobTracker

# Generate SQL script for specific migration range
dotnet ef migrations script <FromMigration> <ToMigration> --project JobTracker

Roadmap

  • Client code generation through Orval
  • Simple frontend based on that client code
  • Simple Ingestor for the JobUp platform
  • Persitance through Postgres EF connection
  • Assigning Applications to users
  • Refactor the frontend to deal with job postings and applications seperatly as was planned in the original specification
  • Selection User bot creation

About

Self hosted automated job application platform

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors