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
- Proposal workflow: New questions from users have
Pending status
- Moderation: Only moderators/admins can approve or reject questions
- Visibility: Rejected questions are hidden from regular users
- Voting: Users can vote once per question (toggleable)
- Learning list: Users can add/remove questions from personal list
- AI answers: Can generate answer via OpenAI, stored in Description
- Ordering: Questions ordered by Order field within category
- Quiz: Only approved questions with non-empty Description from the learning list are eligible for quiz mode
Offers
- Status flow: New → Considered → Sent → Rejected (not strictly sequential)
- Reasons: Status changes can include multiple reasons
- Interviews: Multiple interviews per offer allowed
- User isolation: Each user sees only their own offers
- AI parsing: Offers can be created from URL or pasted text via AI extraction
- Duplication: Existing offers can be duplicated with all fields preserved
- Ordering: Offers can be reordered within status groups via drag & drop
- Tagging: Offers can be tagged with user-defined tags; filter by tags in list view
- AI scoring: Offers can be scored against user profile (0–100); score persisted on offer entity
- AI comparison: 2–5 offers can be compared side-by-side via AI analysis
- Cover letter: AI generates cover letters using offer data + portfolio content
Interviews
- Scheduling: Stages can optionally have a scheduled date
- Stages: Can be in any order (not strictly sequential)
- Status tracking: Scheduled → Completed/Failed/Cancelled
- Notes: Markdown-enabled for preparation notes with dedicated editor dialog
- Calendar sync: Interviews automatically sync with Google Calendar when connected (create/update/delete events)
Portfolio
- Multiple variants: Each user can create multiple CV variants
- Structured data: CV data stored as structured JSON (not free-form text)
- AI import: Portfolios can be populated from LinkedIn URL or PDF upload via AI
- Auto-save: Changes are saved automatically with debounce
- User isolation: Each user sees only their own portfolios
User Profile
- One per user: Single profile record per user, created on first save
- Optional fields: All fields except displayName are optional
- 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 |