Workflows
Question Proposal & Moderation
stateDiagram-v2
[*] --> Pending: User proposes question
Pending --> Approved: Moderator approves
Pending --> Rejected: Moderator rejects
Approved --> Approved: User edits answer
Approved --> Approved: AI generates answer
Rejected --> [*]
note right of Pending: Visible only to moderators
note right of Approved: Visible to all users
note right of Rejected: Hidden from users
Question Voting
sequenceDiagram
participant U as User
participant W as Webapp
participant Q as Questions API
participant DB as PostgreSQL
U->>W: Click "Promote" button
W->>Q: POST /questions/{id}/promote
Q->>DB: Check if user already voted
alt Already voted
Q->>DB: Remove vote, decrement count
else Not voted
Q->>DB: Add vote, increment count
end
Q-->>W: Success
W->>W: Invalidate questions query
W->>U: Update UI (toggle state)
Learning List Management
sequenceDiagram
participant U as User
participant W as Webapp
participant Q as Questions API
U->>W: Click "Add to Learning"
W->>Q: POST /questions/{id}/add-to-learning
Q->>Q: Add to user's learning list
Q-->>W: Success
W->>U: Show "In Learning" state
Note over U,Q: Later...
U->>W: Click "Remove from Learning"
W->>Q: DELETE /questions/{id}/remove-from-learning
Q->>Q: Remove from learning list
Q-->>W: Success
W->>U: Show default state
AI Answer Generation
sequenceDiagram
participant U as User
participant W as Webapp
participant Q as Questions API
participant AI as OpenAI
participant DB as PostgreSQL
U->>W: Click "Ask AI" on question
W->>Q: POST /questions/{id}/ask-ai
Q->>DB: Load question with category context
Q->>AI: ChatCompletion request
Note right of AI: System: "You are an interview expert..."
Note right of AI: User: Question text
AI-->>Q: Generated answer
Q->>DB: Save answer to question.Description
Q-->>W: AskAiResponse with text
W->>W: Invalidate & refetch questions
W->>U: Display markdown answer
Quiz / Flashcard Session
sequenceDiagram
participant U as User
participant W as Webapp
participant Q as Questions API
U->>W: Navigate to /quiz
W->>U: Show QuizSetup (category, count)
U->>W: Select options, click Start
W->>Q: GET /questions/quiz?categoryId=xxx&count=20
Q->>Q: Filter learning list, approved, with answers
Q->>Q: Randomize and limit to count
Q-->>W: QuizQuestionResponse[]
W->>U: Show first flashcard (question side)
loop For each card
U->>W: Click card to flip
W->>U: Show answer side (markdown rendered)
U->>W: Mark as "Known" or "To Review"
U->>W: Navigate to next card
end
W->>U: Show QuizSummary (known vs to-review)
Job Offer Lifecycle
stateDiagram-v2
[*] --> New: Add offer
New --> Considered: Start reviewing
New --> Rejected: Not interested
Considered --> Sent: Submit application
Considered --> Rejected: Decide against
Sent --> Rejected: Application rejected
Sent --> Sent: Schedule interview
Rejected --> [*]
note right of New: Just discovered
note right of Considered: Actively reviewing
note right of Sent: Application submitted
note right of Rejected: No longer pursuing
Adding Job Offer (Manual)
sequenceDiagram
participant U as User
participant W as Webapp
participant O as Organizer API
participant DB as PostgreSQL
U->>W: Click "Add Offer"
W->>U: Show wizard mode selection
U->>W: Choose "Manual entry"
W->>U: Show offer form
U->>W: Fill form (name, link, company, city, ...)
W->>O: POST /organizer/offers
O->>O: Validate request (FluentValidation)
O->>DB: Insert OfferEntity (status: New)
O-->>W: 201 Created
W->>W: Invalidate offers & counts queries
W->>U: Show new offer in list
Adding Job Offer (AI Parsing)
sequenceDiagram
participant U as User
participant W as Webapp
participant O as Organizer API
participant AI as OpenAI
U->>W: Click "Add Offer"
W->>U: Show wizard mode selection
U->>W: Choose "Parse from URL" or "Parse from text"
W->>U: Show input step (URL field or text area)
U->>W: Provide URL or paste text
W->>O: POST /organizer/offers/parse-ai
Note right of O: { mode: "Url"|"Text", content: "..." }
O->>AI: Analyze content, extract offer fields
AI-->>O: Structured offer data (JSON)
O-->>W: ParseOfferAiResponse
W->>U: Show pre-filled form for review
U->>W: Review, edit if needed, confirm
W->>O: POST /organizer/offers
O-->>W: 201 Created
W->>U: Show new offer in list
Offer Duplication
sequenceDiagram
participant U as User
participant W as Webapp
participant O as Organizer API
participant DB as PostgreSQL
U->>W: Click "Duplicate" on offer
W->>O: POST /organizer/offers/{id}/duplicate
O->>DB: Load original offer (verify ownership)
O->>DB: Insert copy with all fields preserved
O-->>W: 201 Created
W->>W: Invalidate offers & counts queries
W->>U: Show duplicated offer in list
Changing Offer Status
sequenceDiagram
participant U as User
participant W as Webapp
participant O as Organizer API
U->>W: Click status dropdown
W->>W: Open ChangeStateDialog
U->>W: Select new status + reasons
W->>O: PATCH /organizer/offers/{id}/state
O->>O: Update status and reasons
O-->>W: Success
W->>W: Invalidate offers & counts
W->>U: Move offer to new status tab
Interview Scheduling
sequenceDiagram
participant U as User
participant W as Webapp
participant O as Organizer API
U->>W: Click "Add Interview" on offer
W->>W: Open AddInterview dialog
U->>W: Select stage, date, location
U->>W: Click "Edit notes" to open markdown editor
W->>U: Show MarkdownEditDialog (editor + preview)
U->>W: Write notes, save
W->>O: POST /organizer/interviews
O->>O: Validate request
O->>O: Create InterviewEntity
O-->>W: 201 Created
W->>W: Invalidate interviews query
W->>U: Show interview in timeline
Interview Status Flow
stateDiagram-v2
[*] --> Scheduled: Schedule interview
Scheduled --> Completed: Interview done
Scheduled --> Failed: Did not pass
Scheduled --> Cancelled: Cancel
Completed --> [*]
Failed --> [*]
Cancelled --> [*]
note right of Scheduled: Upcoming interview
note right of Completed: Passed, awaiting next steps
note right of Failed: Did not pass this stage
Add Interview to Google Calendar (OAuth2)
sequenceDiagram
participant U as User
participant W as Webapp
participant O as Organizer API
participant G as Google Calendar API
Note over U,G: One-time setup
U->>W: Navigate to Settings → Calendar
W->>O: POST /organizer/calendar/connect
O-->>W: Google OAuth URL
W->>G: Redirect to Google consent screen
U->>G: Grant calendar permissions
G->>O: OAuth callback with authorization code
O->>G: Exchange code for tokens
O->>O: Store tokens (GoogleCalendarTokenEntity)
O-->>W: Redirect back to Settings
W->>U: Show "Calendar connected"
Note over U,G: Automatic sync on interview CRUD
U->>W: Add interview with scheduled date
W->>O: POST /organizer/interviews
O->>G: Create calendar event
G-->>O: Event ID
O->>O: Store CalendarEventId on interview
O-->>W: Success
U->>W: Update interview date
W->>O: PUT /organizer/interviews/{id}
O->>G: Update calendar event (or create if missing)
O-->>W: Success
U->>W: Delete interview
W->>O: DELETE /organizer/interviews/{id}
O->>G: Delete calendar event
O-->>W: Success
AI CV-to-Offer Matching
sequenceDiagram
participant U as User
participant W as Webapp
participant O as Organizer API
participant AI as OpenAI
participant DB as PostgreSQL
U->>W: Open offer drawer → "Match CV" tab
W->>U: Show CV variant selector
U->>W: Select CV variant
W->>O: POST /organizer/offers/{id}/match-cv
Note right of O: Body: portfolio data (personal info, experience, skills, ...)
O->>DB: Load offer details (verify ownership)
O->>AI: Analyze CV vs offer requirements
Note right of AI: System prompt: structured JSON response
AI-->>O: { matchScore, summary, strengths, gaps, recommendations }
O-->>W: MatchCvResponse
W->>U: Display score ring + analysis results
AI Offer Scoring
sequenceDiagram
participant U as User
participant W as Webapp
participant O as Organizer API
participant US as Users API
participant AI as OpenAI
participant DB as PostgreSQL
U->>W: Open offer drawer → "Score" tab
U->>W: Click "Score this offer"
W->>O: POST /organizer/offers/{id}/score
O->>US: GET /profile (inter-service call)
US-->>O: User profile (skills, experience, preferred role)
O->>DB: Load offer details
O->>AI: Compare offer requirements vs user profile
AI-->>O: { score: 0-100, breakdown }
O->>DB: Persist MatchScore + MatchScoreBreakdown on offer
O-->>W: ScoreOfferResponse
W->>U: Display score + breakdown
Cover Letter Generation
sequenceDiagram
participant U as User
participant W as Webapp
participant O as Organizer API
participant AI as OpenAI
participant DB as PostgreSQL
U->>W: Open offer drawer → "Cover Letter" tab
U->>W: Select CV variant and tone (formal/creative/concise)
W->>O: POST /organizer/offers/{id}/cover-letter
Note right of O: Body: portfolio data + tone
O->>DB: Load offer details (verify ownership)
O->>AI: Generate cover letter using offer + CV data
Note right of AI: System prompt with tone instructions
AI-->>O: Generated cover letter text (markdown)
O-->>W: CoverLetterResponse
W->>U: Display rendered letter with copy action
Offer Comparison
sequenceDiagram
participant U as User
participant W as Webapp
participant O as Organizer API
participant AI as OpenAI
participant DB as PostgreSQL
U->>W: Select 2-5 offers in the list
U->>W: Click "Compare selected"
W->>O: POST /organizer/offers/compare
Note right of O: Body: { offerIds: [...] }
O->>DB: Load all selected offers with highest interview stages
O->>AI: Compare offers across dimensions
AI-->>O: { offers[], dimensions, ranking, summary }
O-->>W: CompareOffersResponse
W->>U: Show comparison dialog with radar chart
Offer Tagging
sequenceDiagram
participant U as User
participant W as Webapp
participant O as Organizer API
participant DB as PostgreSQL
Note over U,DB: Tag Management
U->>W: Open tag management
U->>W: Create tag (name + color)
W->>O: POST /organizer/tags
O->>DB: Insert TagEntity (unique name per user)
O-->>W: Success
Note over U,DB: Assign Tags to Offer
U->>W: Open offer → click "Tags"
W->>U: Show tag selector with user's tags
U->>W: Select/deselect tags
W->>O: PUT /organizer/offers/{id}/tags
Note right of O: Replace-all strategy: clear existing, add new
O->>DB: Delete existing OfferTags, insert new ones
O-->>W: Success
W->>U: Show updated tags on offer
Portfolio Management
sequenceDiagram
participant U as User
participant W as Webapp
participant P as Portfolio API
participant DB as PostgreSQL
U->>W: Navigate to /portfolio
W->>P: GET /portfolios
P->>DB: Load user's portfolio list
P-->>W: PortfolioSummary[] (id, name, modifiedOn)
W->>U: Show variant selector
U->>W: Select variant (or create new)
W->>P: GET /portfolio?id=xxx
P->>DB: Load full portfolio data
P-->>W: PortfolioResponse (all sections)
W->>U: Show CV editor with all section forms
U->>W: Edit any section (personal info, experience, ...)
W->>W: Auto-save with debounce
W->>P: PUT /portfolio
P->>DB: Upsert portfolio data
P-->>W: 204 No Content
Portfolio AI Import
sequenceDiagram
participant U as User
participant W as Webapp
participant P as Portfolio API
participant AI as OpenAI
U->>W: Click "Import" on portfolio page
W->>U: Show import dialog (LinkedIn URL or PDF)
alt Import from LinkedIn
U->>W: Enter LinkedIn profile URL
W->>P: POST /portfolio/parse-linkedin
P->>AI: Fetch and parse LinkedIn profile
AI-->>P: Structured portfolio data
else Import from PDF
U->>W: Upload CV PDF file
W->>P: POST /portfolio/parse-cv-pdf
P->>AI: Extract text and parse PDF
AI-->>P: Structured portfolio data
end
P-->>W: PortfolioResponse (all sections populated)
W->>U: Show pre-filled CV editor
U->>W: Review, edit if needed
W->>W: Auto-save changes
User Profile Management
sequenceDiagram
participant U as User
participant W as Webapp
participant US as Users API
participant DB as PostgreSQL
U->>W: Navigate to /settings
W->>US: GET /profile
alt Profile exists
US->>DB: Load profile by userId
US-->>W: UserProfileResponse
W->>U: Show populated profile form
else No profile yet
US-->>W: 204 No Content
W->>U: Show empty profile form
end
U->>W: Edit profile fields
U->>W: Click "Save"
W->>US: PUT /profile
US->>DB: Upsert profile (create or update)
US-->>W: 204 No Content
W->>U: Show success confirmation
Category Navigation
sequenceDiagram
participant U as User
participant W as Webapp
participant Q as Questions API
U->>W: Open Questions page
W->>Q: GET /questions/categories
Q-->>W: Hierarchical categories
W->>U: Display main categories
U->>W: Click category
W->>W: Set selectedCategoryId atom
W->>Q: GET /questions?categoryId=xxx
Q-->>W: Questions for category
W->>U: Display questions list
U->>W: Click child category
W->>W: Update selectedCategoryId
W->>Q: GET /questions?categoryId=yyy
Q-->>W: Questions for subcategory
W->>U: Display filtered questions
Authentication Flow
sequenceDiagram
participant U as User
participant W as Webapp
participant K as Keycloak
participant A as API
U->>W: Click "Login"
W->>K: Redirect (Authorization Code + PKCE)
U->>K: Enter credentials
K->>W: Authorization code
W->>K: Exchange for tokens
K->>W: Access + Refresh tokens
W->>W: Store in memory
U->>W: Navigate to /questions
W->>A: GET /questions (Bearer token)
A->>A: Validate JWT
A-->>W: Data
W->>U: Display content
Note over W,K: Token refresh happens automatically