Skip to content

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