# Author: marco, June 2, 2025
openapi: 3.0.3
info:
  title: InNotes API
  description: API documentation for InNotes - A contact and job management platform
  version: 0.1.0
  contact:
    name: InNotes Support
    url: https://innotes.com
servers:
  - url: http://localhost:3000
    description: Development server
  - url: https://innotes.com
    description: Production server

paths:
  /api/user:
    get:
      summary: Get current user information
      description: Retrieve the authenticated user's profile information, including subscription status
      security:
        - bearerAuth: []
      responses:
        '200':
          description: User information retrieved successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/User'
        '401':
          description: Unauthorized - Invalid or missing authentication
        '404':
          description: User not found
        '500':
          description: Internal server error

  /api/contacts:
    get:
      summary: Get contacts
      description: Retrieve contacts with optional pagination, search, and filtering
      security:
        - bearerAuth: []
      parameters:
        - name: page
          in: query
          description: Page number for pagination
          schema:
            type: integer
            default: 1
        - name: search
          in: query
          description: Search term for filtering contacts
          schema:
            type: string
        - name: tags
          in: query
          description: Comma-separated list of tags to filter by
          schema:
            type: string
        - name: tag
          in: query
          description: Individual tag filter (can be used multiple times)
          schema:
            type: array
            items:
              type: string
        - name: ext_id
          in: query
          description: External ID for extension compatibility
          schema:
            type: string
      responses:
        '200':
          description: Contacts retrieved successfully
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Contact'
        '401':
          description: Unauthorized
        '404':
          description: No contacts found
        '500':
          description: Internal server error

    post:
      summary: Create a new contact
      description: Add a new contact to the user's contact list
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ContactInput'
      responses:
        '200':
          description: Contact created successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Contact'
        '400':
          description: Bad request - Missing required fields
        '401':
          description: Unauthorized
        '402':
          description: Payment required - Free user limit exceeded
        '409':
          description: Conflict - Contact with linkedin_key already exists
        '500':
          description: Internal server error

  /api/contacts/{id}:
    get:
      summary: Get contact by ID
      description: Retrieve a specific contact by its ID
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          description: Contact ID
          schema:
            type: string
      responses:
        '200':
          description: Contact retrieved successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Contact'
        '401':
          description: Unauthorized
        '404':
          description: Contact not found
        '500':
          description: Internal server error

    put:
      summary: Update contact
      description: Update an existing contact
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          description: Contact ID
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ContactInput'
      responses:
        '200':
          description: Contact updated successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Contact'
        '400':
          description: Bad request
        '401':
          description: Unauthorized
        '404':
          description: Contact not found
        '500':
          description: Internal server error

    delete:
      summary: Delete contact
      description: Delete a contact from the user's contact list
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          description: Contact ID
          schema:
            type: string
      responses:
        '200':
          description: Contact deleted successfully
        '401':
          description: Unauthorized
        '404':
          description: Contact not found
        '500':
          description: Internal server error

  /api/job:
    get:
      summary: Get jobs
      description: Retrieve job applications with optional search and filtering
      security:
        - bearerAuth: []
      parameters:
        - name: ext_id
          in: query
          description: External ID for extension compatibility
          schema:
            type: string
        - name: searchTerm
          in: query
          description: Search term for filtering jobs
          schema:
            type: string
      responses:
        '200':
          description: Jobs retrieved successfully
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Job'
        '401':
          description: Unauthorized
        '500':
          description: Internal server error

    post:
      summary: Create a new job
      description: Add a new job application
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/JobInput'
      responses:
        '201':
          description: Job created successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  id:
                    type: string
                    description: ID of the created job
        '401':
          description: Unauthorized
        '402':
          description: Payment required - Free user limit exceeded
        '409':
          description: Conflict - Job with ext_id already exists
        '500':
          description: Internal server error

  /api/job/{id}:
    get:
      summary: Get job by ID
      description: Retrieve a specific job by its ID
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          description: Job ID
          schema:
            type: string
      responses:
        '200':
          description: Job retrieved successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Job'
        '401':
          description: Unauthorized
        '404':
          description: Job not found
        '500':
          description: Internal server error

    put:
      summary: Update job
      description: Update an existing job application
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          description: Job ID
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/JobInput'
      responses:
        '200':
          description: Job updated successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Job'
        '400':
          description: Bad request
        '401':
          description: Unauthorized
        '404':
          description: Job not found
        '500':
          description: Internal server error

    delete:
      summary: Delete job
      description: Delete a job application
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          description: Job ID
          schema:
            type: string
      responses:
        '200':
          description: Job deleted successfully
        '401':
          description: Unauthorized
        '404':
          description: Job not found
        '500':
          description: Internal server error

  /api/note:
    get:
      summary: Get notes
      description: Retrieve notes for a specific contact or job
      security:
        - bearerAuth: []
      parameters:
        - name: linkedinDataId
          in: query
          description: LinkedIn data ID (deprecated, use ext_id)
          schema:
            type: string
        - name: ext_id
          in: query
          description: External ID of the related entity
          schema:
            type: string
      responses:
        '200':
          description: Notes retrieved successfully
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Note'
        '401':
          description: Unauthorized
        '500':
          description: Internal server error

    post:
      summary: Create a new note
      description: Add a new note to a contact or job
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/NoteInput'
      responses:
        '201':
          description: Note created successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  id:
                    type: string
                    description: ID of the created note
        '400':
          description: Bad request - ext_id is required
        '401':
          description: Unauthorized
        '500':
          description: Internal server error

  /api/search:
    get:
      summary: Search notes
      description: Search through notes content
      security:
        - bearerAuth: []
      parameters:
        - name: searchTerm
          in: query
          required: true
          description: Search term for filtering notes
          schema:
            type: string
      responses:
        '200':
          description: Search results retrieved successfully
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/SearchResult'
        '400':
          description: Bad request - Search term is required
        '401':
          description: Unauthorized
        '500':
          description: Internal server error

  /api/tags:
    get:
      summary: Get all tags
      description: Retrieve all unique tags for the authenticated user
      security:
        - bearerAuth: []
      responses:
        '200':
          description: Tags retrieved successfully
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Tag'
        '401':
          description: Unauthorized
        '500':
          description: Internal server error

  /api/statuses:
    get:
      summary: Get all statuses
      description: Retrieve all contact statuses for the authenticated user
      security:
        - bearerAuth: []
      responses:
        '200':
          description: Statuses retrieved successfully
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Status'
        '401':
          description: Unauthorized
        '500':
          description: Internal server error

    post:
      summary: Create a new status
      description: Add a new contact status
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/StatusInput'
      responses:
        '201':
          description: Status created successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Status'
        '400':
          description: Bad request
        '401':
          description: Unauthorized
        '500':
          description: Internal server error

  /api/statuses/{id}:
    put:
      summary: Update status
      description: Update an existing contact status
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          description: Status ID
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/StatusInput'
      responses:
        '200':
          description: Status updated successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Status'
        '400':
          description: Bad request
        '401':
          description: Unauthorized
        '404':
          description: Status not found
        '500':
          description: Internal server error

    delete:
      summary: Delete status
      description: Delete a contact status
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          description: Status ID
          schema:
            type: string
      responses:
        '200':
          description: Status deleted successfully
        '401':
          description: Unauthorized
        '404':
          description: Status not found
        '500':
          description: Internal server error

  /api/feedback:
    post:
      summary: Submit feedback
      description: Submit user feedback
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/FeedbackInput'
      responses:
        '200':
          description: Feedback submitted successfully
        '400':
          description: Bad request
        '401':
          description: Unauthorized
        '500':
          description: Internal server error

  /api/features:
    get:
      summary: Get feature requests
      description: Retrieve all feature requests
      security:
        - bearerAuth: []
      responses:
        '200':
          description: Feature requests retrieved successfully
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Feature'
        '401':
          description: Unauthorized
        '500':
          description: Internal server error

    post:
      summary: Create feature request
      description: Submit a new feature request
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/FeatureInput'
      responses:
        '201':
          description: Feature request created successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Feature'
        '400':
          description: Bad request
        '401':
          description: Unauthorized
        '500':
          description: Internal server error

  /api/features/vote:
    post:
      summary: Vote on feature request
      description: Vote for or against a feature request
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/VoteInput'
      responses:
        '200':
          description: Vote submitted successfully
        '400':
          description: Bad request
        '401':
          description: Unauthorized
        '500':
          description: Internal server error

  /api/features/bug:
    post:
      summary: Report bug
      description: Submit a bug report
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/BugReportInput'
      responses:
        '200':
          description: Bug report submitted successfully
        '400':
          description: Bad request
        '401':
          description: Unauthorized
        '500':
          description: Internal server error

components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT

  schemas:
    User:
      type: object
      properties:
        id:
          type: string
          description: User ID
        username:
          type: string
          description: Username
        email:
          type: string
          nullable: true
          description: User email
        name:
          type: string
          description: User full name
        status:
          type: string
          enum: [free, premium]
          description: User subscription status
        created_at:
          type: string
          format: date-time
          description: Account creation date
        subscription_id:
          type: string
          nullable: true
          description: Stripe subscription ID
        home_location:
          type: string
          nullable: true
          description: User's home location
        picture:
          type: string
          nullable: true
          description: Profile picture URL
        cv:
          type: string
          nullable: true
          description: CV/Resume content
        cv_updated_at:
          type: string
          format: date-time
          nullable: true
          description: CV last update timestamp
        job_preferences:
          type: string
          nullable: true
          description: JSON string of job preferences
        job_expiration_days:
          type: integer
          description: Days after which jobs expire
        hide_closed_lost_days:
          type: integer
          description: Days after which to hide closed/lost jobs

    Contact:
      type: object
      properties:
        id:
          type: string
          description: Contact ID
        linkedin_user:
          type: string
          nullable: true
          description: LinkedIn username
        linkedin_key:
          type: string
          nullable: true
          description: LinkedIn profile key
        name:
          type: string
          nullable: true
          description: Contact name
        status_id:
          type: integer
          nullable: true
          description: Contact status ID
        note:
          type: string
          nullable: true
          description: Contact note
        tags:
          type: array
          items:
            $ref: '#/components/schemas/Tag'
          nullable: true
          description: Contact tags
        notes_count:
          type: integer
          description: Number of notes for this contact
        picture_url:
          type: string
          nullable: true
          description: Profile picture URL
        location:
          type: string
          nullable: true
          description: Contact location
        current_company:
          type: string
          nullable: true
          description: Current company
        created_at:
          type: string
          format: date-time
          description: Contact creation date
        updated_at:
          type: string
          format: date-time
          description: Contact last update date

    ContactInput:
      type: object
      required:
        - linkedin_key
      properties:
        linkedin_key:
          type: string
          description: LinkedIn profile key
        linkedin_user:
          type: string
          description: LinkedIn username
        name:
          type: string
          description: Contact name
        status_id:
          type: integer
          description: Contact status ID
        tags:
          type: array
          items:
            type: string
          description: Contact tags
        picture_url:
          type: string
          description: Profile picture URL
        location:
          type: string
          description: Contact location
        current_company:
          type: string
          description: Current company

    Job:
      type: object
      properties:
        id:
          type: string
          description: Job ID
        ext_id:
          type: string
          description: External ID
        name:
          type: string
          description: Job title
        company_name:
          type: string
          description: Company name
        location:
          type: string
          description: Job location
        url:
          type: string
          format: uri
          description: Job posting URL
        provider:
          type: string
          description: Job source provider
          enum:
            - linkedin
            - external
            - company
            - referral
            - indeed
            - glassdoor
            - jobsch
        description:
          type: string
          description: Job description
        status:
          type: string
          description: Application status
        status_id:
          type: integer
          description: Application status identifier
        notes:
          type: string
          description: Application notes
        tags:
          type: array
          items:
            type: string
          description: Tags associated with the job
        created_at:
          type: string
          format: date-time
          description: Job creation date
        updated_at:
          type: string
          format: date-time
          description: Job last update date

    JobInput:
      type: object
      properties:
        name:
          type: string
          description: Job title
        company_name:
          type: string
          description: Company name
        ext_id:
          type: string
          description: External ID
        location:
          type: string
          description: Job location
        url:
          type: string
          format: uri
          description: Job posting URL
        provider:
          type: string
          description: Job source provider
          enum:
            - linkedin
            - external
            - company
            - referral
            - indeed
            - glassdoor
            - jobsch
        description:
          type: string
          description: Job description
        status:
          type: string
          description: Application status
        status_id:
          type: integer
          description: Status identifier
        notes:
          type: string
          description: Application notes
        tags:
          type: array
          description: Tags associated with the job
          items:
            type: string

    Note:
      type: object
      properties:
        id:
          type: string
          description: Note ID
        text:
          type: string
          description: Note content
        ext_id:
          type: string
          description: External ID of related entity
        ext_table_name:
          type: string
          description: Table name of related entity
        flag_color:
          type: string
          nullable: true
          description: Flag color for the note
        created_at:
          type: string
          format: date-time
          description: Note creation date
        updated_at:
          type: string
          format: date-time
          description: Note last update date

    NoteInput:
      type: object
      required:
        - ext_id
      properties:
        text:
          type: string
          description: Note content
        ext_id:
          type: string
          description: External ID of related entity
        ext_table_name:
          type: string
          default: contacts
          description: Table name of related entity
        flag_color:
          type: string
          description: Flag color for the note

    SearchResult:
      type: object
      properties:
        id:
          type: string
          description: Note ID
        text:
          type: string
          description: Note content
        flag_color:
          type: string
          nullable: true
          description: Flag color
        last_update:
          type: string
          format: date-time
          description: Last update timestamp
        linkedin_user:
          type: string
          nullable: true
          description: Contact LinkedIn username
        linkedin_key:
          type: string
          nullable: true
          description: Contact LinkedIn key
        contact_name:
          type: string
          nullable: true
          description: Contact name
        picture_url:
          type: string
          nullable: true
          description: Contact picture URL

    Tag:
      type: object
      properties:
        id:
          type: string
          description: Tag ID
        name:
          type: string
          description: Tag name
        color:
          type: string
          description: Tag color

    Status:
      type: object
      properties:
        id:
          type: integer
          description: Status ID
        name:
          type: string
          description: Status name
        color:
          type: string
          description: Status color
        user_id:
          type: string
          description: User ID

    StatusInput:
      type: object
      required:
        - name
      properties:
        name:
          type: string
          description: Status name
        color:
          type: string
          description: Status color

    FeedbackInput:
      type: object
      required:
        - message
      properties:
        message:
          type: string
          description: Feedback message
        type:
          type: string
          enum: [feedback, bug, feature]
          description: Feedback type

    Feature:
      type: object
      properties:
        id:
          type: string
          description: Feature ID
        title:
          type: string
          description: Feature title
        description:
          type: string
          description: Feature description
        votes:
          type: integer
          description: Number of votes
        status:
          type: string
          description: Feature status
        created_at:
          type: string
          format: date-time
          description: Feature creation date

    FeatureInput:
      type: object
      required:
        - title
        - description
      properties:
        title:
          type: string
          description: Feature title
        description:
          type: string
          description: Feature description

    VoteInput:
      type: object
      required:
        - feature_id
        - vote
      properties:
        feature_id:
          type: string
          description: Feature ID to vote on
        vote:
          type: string
          enum: [up, down]
          description: Vote direction

    BugReportInput:
      type: object
      required:
        - title
        - description
      properties:
        title:
          type: string
          description: Bug title
        description:
          type: string
          description: Bug description
        steps_to_reproduce:
          type: string
          description: Steps to reproduce the bug
        expected_behavior:
          type: string
          description: Expected behavior
        actual_behavior:
          type: string
          description: Actual behavior

    Error:
      type: object
      properties:
        error:
          type: string
          description: Error message

security:
  - bearerAuth: []
