Skip to content

Domain Model

Bounded Contexts

graph TB
    subgraph Questions Context
        C[Category]
        Q[Question]
        V[Vote]
        L[Learning]
    end

    subgraph Organizer Context
        O[Offer]
        I[Interview]
        T[Tag]
        OT[OfferTag]
        D[Dictionary]
        GC[GoogleCalendarToken]
    end

    subgraph Portfolio Context
        P[Portfolio]
    end

    subgraph Identity Context
        U[User]
        R[Role]
        UP[UserProfile]
    end

    Q --> C
    V --> Q
    V --> U
    L --> Q
    L --> U
    I --> O
    OT --> O
    OT --> T
    GC --> U
    O --> U
    P --> U
    UP --> U

Questions Context

Category

Hierarchical grouping of interview questions.

Field Type Description
Id Guid Unique identifier
Name string Category name
Description string? Optional description
IconName string? Icon identifier for UI
Order int Display order
ParentCategoryId Guid? Parent for hierarchy
ChildCategories Category[] Nested categories

Question

Interview question with voting and learning tracking.

Field Type Description
Id Guid Unique identifier
CategoryId Guid Parent category
Name string Question text
Description string? Answer/explanation (supports markdown)
Order int Display order within category
Status QuestionStatus Moderation status
PromoteCount int Number of upvotes
CreatedBy Guid User who created
CreatedOn DateTimeOffset Creation timestamp

QuestionVote

Tracks user votes on questions.

Field Type Description
Id Guid Unique identifier
QuestionId Guid Voted question
UserId Guid Voting user
VotedOn DateTimeOffset Vote timestamp

LearningQuestion

User's personal learning list.

Field Type Description
Id Guid Unique identifier
QuestionId Guid Question to learn
UserId Guid User
AddedOn DateTimeOffset When added

Organizer Context

Offer

Job offer being tracked.

Field Type Description
Id Guid Unique identifier
Name string Job title
Link string URL to job posting
Company string Company name
City string Location
Status OfferStatus Current status
Order int Display order within status group
Reasons string[] Status change reasons
Description string? Notes (supports markdown)
Source OfferSource? Where the offer was found
WorkMode WorkMode? Remote, Hybrid, or Onsite
Intermediary string? Recruitment agency
SalaryMin decimal? Minimum salary
SalaryMax decimal? Maximum salary
SalaryCurrency SalaryCurrency? PLN, EUR, USD
SalaryType SalaryType? Monthly, Hourly, Annual
IsAiGenerated bool Created via AI parsing
RawContent string? Original content for AI parsing
SourceUrl string? Original job posting URL
MatchScore int? AI-generated match score (0–100)
MatchScoreBreakdown string? AI explanation of the score
Interviews Interview[] Scheduled interviews
Tags Tag[] User-assigned tags (via OfferTag)
CreatedBy Guid User tracking this offer
CreatedOn DateTimeOffset When added
ModifiedBy Guid Last modifier
ModifiedOn DateTimeOffset Last modification

Interview

Scheduled interview for an offer.

Field Type Description
Id Guid Unique identifier
OfferId Guid Parent offer
Stage InterviewStage Interview type
Status InterviewStatus Current status
ScheduledAt DateTimeOffset? Date and time
Duration int? Duration in minutes
Notes string? Preparation notes (supports markdown)
Location string? Address or video link
CalendarEventId string? Google Calendar event ID for sync
CreatedBy Guid User who created
CreatedOn DateTimeOffset When added

KeyValueList

Reference data dictionary.

Field Type Description
Key DictionaryKey Type of data
Values string[] Available options

Tag

User-defined tag for categorizing offers.

Field Type Description
Id Guid Unique identifier
Name string Tag name (unique per user)
Color string HEX color code (e.g. #FF5733)
CreatedBy Guid Owner user
CreatedOn DateTimeOffset When created

OfferTag

Many-to-many join between offers and tags.

Field Type Description
Id Guid Unique identifier
OfferId Guid Linked offer
TagId Guid Linked tag

GoogleCalendarToken

OAuth2 token storage for Google Calendar integration. One token per user.

Field Type Description
Id Guid Unique identifier
AccessToken string Google OAuth access token
RefreshToken string Google OAuth refresh token
ExpiresAt DateTimeOffset Token expiration time
CalendarId string? Selected calendar ID
CreatedBy Guid Owner user
CreatedOn DateTimeOffset When connected

Portfolio Context

Portfolio

User's CV/resume data, stored as a structured JSON document. Supports multiple variants per user.

Field Type Description
Id Guid Unique identifier
Name string Variant name (e.g. "Backend Developer")
Data PortfolioData Structured CV content (JSON column)
CreatedBy Guid Owner user
CreatedOn DateTimeOffset When created
ModifiedBy Guid Last modifier
ModifiedOn DateTimeOffset Last modification

PortfolioData (JSON structure)

Section Fields
PersonalInfo firstName, lastName, email, phone, location, linkedIn, website
Summary Professional summary text
Experience[] id, company, position, startDate, endDate, current, description
Projects[] id, name, description, technologies[], link
Skills[] id, name, level (Beginner/Intermediate/Advanced/Expert)
Education[] id, institution, degree, field, startDate, endDate
Certifications[] id, name, issuer, date, link
Languages[] id, name, level (A1/A2/B1/B2/C1/C2/Native)

Identity Context

UserProfile

Editable user profile stored in the Users.Api database. One profile per user.

Field Type Description
Id Guid Unique identifier
DisplayName string Display name
Bio string? Short biography
PreferredRole string? Preferred job role
ExperienceYears int? Years of experience
Location string? City or region
Skills string? Comma-separated skills
LinkedInUrl string? LinkedIn profile URL
GitHubUrl string? GitHub profile URL
WebsiteUrl string? Personal website URL
CreatedBy Guid Owner user
CreatedOn DateTimeOffset When created
ModifiedBy Guid Last modifier
ModifiedOn DateTimeOffset Last modification

Enums

QuestionStatus

public enum QuestionStatus
{
    Approved = 0,   // Visible to all users
    Pending = 1,    // Awaiting moderator approval
    Rejected = 2    // Hidden from regular users
}

OfferStatus

public enum OfferStatus
{
    New,        // Just added, not acted upon
    Considered, // Reviewing the offer
    Sent,       // Application submitted
    Rejected    // No longer pursuing
}

InterviewStage

public enum InterviewStage
{
    Screening,   // Initial phone/video screen
    Technical,   // Technical assessment
    HR,          // HR interview
    Offer,       // Offer discussion
    Onboarding   // Post-acceptance
}

InterviewStatus

public enum InterviewStatus
{
    Scheduled,   // Upcoming
    Completed,   // Done, awaiting result
    Failed,      // Did not pass
    Cancelled    // Cancelled by either party
}

DictionaryKey

public enum DictionaryKey
{
    Cities,     // Available cities
    Positions,  // Job position types
    Reasons     // Rejection/status change reasons
}

OfferSource

public enum OfferSource
{
    LinkedIn,
    NoFluffJobs,
    JustJoinIT,
    Pracuj,
    Email,
    Direct,
    Other
}

WorkMode

public enum WorkMode
{
    Remote,
    Hybrid,
    Onsite
}

SalaryCurrency

public enum SalaryCurrency
{
    PLN,
    EUR,
    USD
}

SalaryType

public enum SalaryType
{
    Monthly,
    Hourly,
    Annual
}

SkillLevel (Portfolio)

Beginner, Intermediate, Advanced, Expert

LanguageLevel (Portfolio)

A1, A2, B1, B2, C1, C2, Native

Aggregates

Question Aggregate

classDiagram
    class Category {
        +Guid Id
        +string Name
        +Category[] ChildCategories
        +Question[] Questions
    }

    class Question {
        +Guid Id
        +string Name
        +string? Description
        +QuestionStatus Status
        +int PromoteCount
        +Vote[] Votes
        +Promote(userId)
        +Approve()
        +Reject()
    }

    class Vote {
        +Guid Id
        +Guid UserId
        +DateTimeOffset VotedOn
    }

    Category "1" --> "*" Category : children
    Category "1" --> "*" Question
    Question "1" --> "*" Vote

Offer Aggregate

classDiagram
    class Offer {
        +Guid Id
        +string Name
        +string Company
        +OfferStatus Status
        +int Order
        +bool IsAiGenerated
        +int? MatchScore
        +Interview[] Interviews
        +Tag[] Tags
        +UpdateStatus(status, reasons)
        +AddInterview(stage, scheduledAt)
        +Duplicate()
        +ScoreOffer()
    }

    class Interview {
        +Guid Id
        +InterviewStage Stage
        +InterviewStatus Status
        +DateTimeOffset? ScheduledAt
        +int? Duration
        +string? Notes
        +string? Location
        +string? CalendarEventId
        +Complete()
        +Cancel()
    }

    class Tag {
        +Guid Id
        +string Name
        +string Color
    }

    Offer "1" --> "*" Interview
    Offer "*" --> "*" Tag : via OfferTag

Portfolio Aggregate

classDiagram
    class Portfolio {
        +Guid Id
        +string Name
        +PortfolioData Data
        +Save()
        +Delete()
    }

    class PortfolioData {
        +PersonalInfo PersonalInfo
        +string Summary
        +Experience[] Experience
        +Project[] Projects
        +Skill[] Skills
        +Education[] Education
        +Certification[] Certifications
        +Language[] Languages
    }

    Portfolio "1" --> "1" PortfolioData

Business Rules

Questions

  1. Proposal workflow: New questions from users have Pending status
  2. Moderation: Only moderators/admins can approve or reject questions
  3. Visibility: Rejected questions are hidden from regular users
  4. Voting: Users can vote once per question (toggleable)
  5. Learning list: Users can add/remove questions from personal list
  6. AI answers: Can generate answer via OpenAI, stored in Description
  7. Ordering: Questions ordered by Order field within category
  8. Quiz: Only approved questions with non-empty Description from the learning list are eligible for quiz mode

Offers

  1. Status flow: New → Considered → Sent → Rejected (not strictly sequential)
  2. Reasons: Status changes can include multiple reasons
  3. Interviews: Multiple interviews per offer allowed
  4. User isolation: Each user sees only their own offers
  5. AI parsing: Offers can be created from URL or pasted text via AI extraction
  6. Duplication: Existing offers can be duplicated with all fields preserved
  7. Ordering: Offers can be reordered within status groups via drag & drop
  8. Tagging: Offers can be tagged with user-defined tags; filter by tags in list view
  9. AI scoring: Offers can be scored against user profile (0–100); score persisted on offer entity
  10. AI comparison: 2–5 offers can be compared side-by-side via AI analysis
  11. Cover letter: AI generates cover letters using offer data + portfolio content

Interviews

  1. Scheduling: Stages can optionally have a scheduled date
  2. Stages: Can be in any order (not strictly sequential)
  3. Status tracking: Scheduled → Completed/Failed/Cancelled
  4. Notes: Markdown-enabled for preparation notes with dedicated editor dialog
  5. Calendar sync: Interviews automatically sync with Google Calendar when connected (create/update/delete events)

Portfolio

  1. Multiple variants: Each user can create multiple CV variants
  2. Structured data: CV data stored as structured JSON (not free-form text)
  3. AI import: Portfolios can be populated from LinkedIn URL or PDF upload via AI
  4. Auto-save: Changes are saved automatically with debounce
  5. User isolation: Each user sees only their own portfolios

User Profile

  1. One per user: Single profile record per user, created on first save
  2. Optional fields: All fields except displayName are optional
  3. Cross-feature: Profile data is referenced by other features (e.g. AI matching)

User Roles

Role Capabilities
user View questions, vote, learning list, propose questions, quiz, manage own offers, manage portfolio, manage profile
moderator All user capabilities + approve/reject proposed questions
admin All capabilities + system management