Skip to content

Latest commit

 

History

History
1223 lines (970 loc) · 46.7 KB

File metadata and controls

1223 lines (970 loc) · 46.7 KB

API

Important

All API requests must include an Authorization header in the following format: Authorization: Bearer <BEARER_TOKEN>

Rate Limit: Configurable via RATE_LIMIT environment variable (default 5) requests per minute per IP address All endpoints return JSON responses with appropriate HTTP status codes

/api/manual-generate/

Endpoint: /think-root/api/manual-generate/

Method: POST

Description: This endpoint is used to manually generate description for a provided repository URL, and add it to the database. Supports multilingual text generation.

Curl Example:

curl -X POST \
  'http://localhost:8080/think-root/api/manual-generate/' \
  -H 'Authorization: Bearer <BEARER_TOKEN>' \
  -H 'Content-Type: application/json' \
  -d '{
    "url": "https://github.com/example/repo",
    "llm_output_language": "en,uk,fr",
    "llm_provider": "mistral_api",
    "llm_config": {
      "model": "mistral-small-latest"
    }
  }'

Request Parameters:

Parameter Type Required Description
url string Yes GitHub repository URL. Supports multiple whitespace-separated URLs to process in a single request.
llm_output_language string No Comma-separated language codes (e.g., "en,uk,fr"). Default: "uk".
llm_provider string No LLM provider name. Values: mistral_api (default), openai, openrouter, chutes. If omitted, defaults to mistral_api.
llm_config object Yes* Provider configuration. Required for all providers to specify at least the model. See llm_config Structure section below.
use_direct_url boolean No If true, the URL string is used directly as LLM input instead of README content.

* llm_config is required for all providers to specify the model.

llm_config Structure:

The llm_config object is passed to the LLM provider's chat completion API. Common parameters:

  • model: (Required) The model ID (e.g., mistral-small-latest, gpt-4o, google/gemini-2.0-flash-exp:free, moonshotai/Kimi-K2-Instruct-0905).
  • temperature: (Optional) Sampling temperature (0.0 to 1.0).
  • max_tokens: (Optional) Maximum tokens to generate.
  • top_p: (Optional) Nucleus sampling probability.
  • messages: (Optional) Array of message objects to specify a custom prompt. See below.

Custom Prompt:

You can provide a custom system prompt via the messages array. The server will append its multilingual instructions to your prompt:

"llm_config": {
  "model": "mistral-small-latest",
  "messages": [
    {
      "role": "system",
      "content": "Your custom prompt here. Describe repos in a fun, engaging way."
    }
  ]
}

If you don't provide a messages array, the server creates one with default multilingual instructions.

Request Examples:

  1. Basic request with Mistral (default provider):
{
  "url": "https://github.com/example/repo",
  "llm_config": {
    "model": "mistral-small-latest"
  }
}
  1. Request with Chutes provider:
{
  "url": "https://github.com/example/repo",
  "llm_provider": "chutes",
  "llm_output_language": "en",
  "llm_config": {
    "model": "moonshotai/Kimi-K2-Instruct-0905"
  }
}
  1. Multilingual request with OpenRouter:
{
  "url": "https://github.com/example/repo",
  "llm_output_language": "en,uk,fr",
  "llm_provider": "openrouter",
  "llm_config": {
    "model": "google/gemini-2.0-flash-exp:free"
  }
}

Status Codes:

  • 200: Success
  • 400: Invalid request
  • 401: Unauthorized

Response Fields:

Field Type Description
status string Response status: ok (all succeeded), partial (some failed), error (all failed)
added array[string] URLs of repositories successfully added to the database
dont_added array[string] URLs of repositories that failed to process
error_message string (optional) General error message when failures occur
error_details object (optional) Detailed error information for each failed repository (URL → ErrorDetail mapping)

ErrorDetail Object Structure:

Field Type Description
type string Error type: invalid_url, already_exists, no_readme, insufficient_content, non_english_readme, low_quality or processing_error
message string Human-readable error message describing what went wrong

Error Types:

  • invalid_url: The value is not a GitHub repository URL. Any GitHub URL form is accepted (missing scheme, http://, www., tracking query parameters, a .git suffix, a deep path such as /tree/main/src) and normalized to https://github.com/<owner>/<repo>; the reported key is the original input
  • already_exists: Repository already exists in the database
  • no_readme: README file not found in the repository (not reported when use_direct_url is enabled — the README is then optional)
  • insufficient_content: README has less meaningful content than README_MIN_CONTENT_LENGTH (default 150); the message includes the measured length
  • non_english_readme: README is not predominantly written in a Latin script — more than README_MAX_NON_LATIN_PERCENT (default 20) percent of its letters are non-Latin; the message includes the measured share
  • low_quality: The generated description was rejected as empty, shorter than MIN_DESCRIPTION_LENGTH (default 40) or as an LLM refusal; the message includes the exact reason
  • processing_error: LLM processing or database insertion failed

Response Examples:

Success:

{
  "status": "ok",
  "added": ["https://github.com/example/repo"],
  "dont_added": []
}

Single error with details:

{
  "status": "error",
  "added": [],
  "dont_added": ["https://github.com/example/repo"],
  "error_message": "All repositories failed to process",
  "error_details": {
    "https://github.com/example/repo": {
      "type": "already_exists",
      "message": "Repository already exists in database"
    }
  }
}

Multiple errors with different types:

{
  "status": "partial",
  "added": ["https://github.com/example/success"],
  "dont_added": [
    "https://github.com/example/existing",
    "https://github.com/example/no-docs",
    "https://github.com/example/failed"
  ],
  "error_message": "3 repositories failed to process",
  "error_details": {
    "https://github.com/example/existing": {
      "type": "already_exists",
      "message": "Repository already exists in database"
    },
    "https://github.com/example/no-docs": {
      "type": "no_readme",
      "message": "README file not found in repository"
    },
    "https://github.com/example/failed": {
      "type": "processing_error",
      "message": "LLM processing failed"
    }
  }
}

Migration Notes:

The error_details field is backward compatible:

  • Existing clients that don't expect this field will continue to work without changes
  • The field is optional (omitempty) and only present when errors occur
  • All existing response fields (status, added, dont_added, error_message) remain unchanged
  • New clients can use error_details to provide more specific error feedback to users

/api/auto-generate/

Endpoint: /think-root/api/auto-generate/

Method: POST

Description: This endpoint is used to automatically parse trending repositories and generate description based on certain parameters. It also adds the generated posts to the database. Supports multilingual text generation. Supports multiple data sources (GitHub, OssInsight).

Curl Example:

curl -X POST \
  'http://localhost:8080/think-root/api/auto-generate/' \
  -H 'Authorization: Bearer <BEARER_TOKEN>' \
  -H 'Content-Type: application/json' \
  -d '{
    "max_repos": 5,
    "resource": "github",
    "since": "weekly",
    "spoken_language_code": "en",
    "llm_output_language": "en,uk,fr",
    "llm_provider": "mistral_api",
    "llm_config": {
      "model": "mistral-small-latest"
    }
  }'

Request Parameters:

Parameter Type Required Description
max_repos integer Yes Maximum number of repositories to process. Must be > 0.
resource string No Data source. Values: github (default), ossinsight.
since string No For GitHub resource: Time period for trending repos (daily, weekly, monthly).
spoken_language_code string No For GitHub resource: Spoken language filter for GitHub Trending.
period string No For OssInsight resource: Time period (past_24_hours, past_week, past_month, past_3_months). Default: past_24_hours.
language string No For OssInsight resource: Programming language filter (e.g., Python, All). Default: All.
llm_output_language string No Comma-separated language codes for output (e.g., en,uk,fr). Default: uk.
llm_provider string No LLM provider name. Values: mistral_api (default), openai, openrouter, chutes. If omitted, defaults to mistral_api.
llm_config object Yes* Provider configuration. Required for all providers to specify at least the model.
use_direct_url boolean No If true, the repository URL string is used directly as LLM input instead of README content.

* llm_config is required for all providers to specify the model.

llm_config Structure:

The llm_config object is passed as the JSON body to the chosen LLM provider's chat completion API (e.g., OpenAI, Mistral, OpenRouter). Common parameters include:

  • model: (Required) The ID of the model to use (e.g., gpt-4o, mistral-large-latest, google/gemini-2.0-flash-exp:free).
  • temperature: (Optional) Sampling temperature (0.0 to 1.0). Higher values mean more random output.
  • max_tokens: (Optional) The maximum number of tokens to generate.
  • top_p: (Optional) Nucleus sampling probability.
  • stream: (Optional) Boolean to stream responses (usually false for this API).
  • messages: (Optional) Array of message objects to specify a custom prompt. See below.

Custom Prompt:

You can provide a custom system prompt via the messages array. The server will append its multilingual instructions to your custom prompt:

"llm_config": {
  "model": "google/gemini-2.0-flash-exp:free",
  "messages": [
    {
      "role": "system",
      "content": "Your custom prompt here. Be concise and creative."
    }
  ]
}

If you don't provide a messages array, the server creates one with default multilingual instructions based on llm_output_language.

Request Examples:

  1. Basic request (GitHub source):
{
  "max_repos": 5,
  "resource": "github",
  "since": "weekly",
  "spoken_language_code": "en",
  "llm_provider": "mistral_api",
  "llm_config": {
    "model": "mistral-tiny"
  }
}
  1. Request with Chutes.ai provider:
{
  "max_repos": 3,
  "since": "daily",
  "spoken_language_code": "en",
  "llm_provider": "chutes",
  "llm_output_language": "uk,en",
  "llm_config": {
    "model": "moonshotai/Kimi-K2-Instruct-0905",
    "temperature": 0.5,
    "max_tokens": 1024
  }
}
  1. Multilingual request with OpenRouter:
{
  "max_repos": 5,
  "since": "weekly",
  "spoken_language_code": "en",
  "llm_output_language": "en,uk,fr",
  "llm_provider": "openrouter",
  "llm_config": {
    "model": "google/gemini-2.0-flash-exp:free"
  }
}
  1. Request using OssInsight source:
{
  "max_repos": 10,
  "resource": "ossinsight",
  "period": "past_month",
  "language": "Python",
  "llm_provider": "mistral_api",
  "llm_config": {
    "model": "mistral-small"
  }
}

Response Example:

{
  "status": "ok",
  "added": ["https://github.com/example/repo1", "https://github.com/example/repo2"],
  "dont_added": ["https://github.com/example/repo3"]
}

/api/get-repository/

Endpoint: /think-root/api/get-repository/

Method: POST

Description: This endpoint retrieves a list of repositories based on the provided limit, posted status, and sorting preferences. Results can be sorted by different fields and directions, with special handling for null values in publication dates. By default, if text_language is omitted, the endpoint returns the raw multilingual text exactly as stored, e.g., "===(en)text===(uk)текст===". If text_language is provided (e.g., "en" or "uk"), the endpoint returns only that language’s text. If the requested language is not available, the request still succeeds and falls back to the Ukrainian text, or to the first language stored.

Curl Example:

curl -X POST \
  'http://localhost:8080/think-root/api/get-repository/' \
  -H 'Authorization: Bearer <BEARER_TOKEN>' \
  -H 'Content-Type: application/json' \
  -d '{
    "limit": 1,
    "posted": false,
    "sort_by": "date_added",
    "sort_order": "DESC",
    "text_language": "uk"
  }'

Request Parameters:

Parameter Type Required Description
limit integer No Maximum number of repositories to return. Set to 0 to either return all records (if page and page_size are not specified) or use pagination mode (if page or page_size are specified).
posted boolean No Filter repositories by posted status. If not specified and limit is 0, returns all records regardless of posted status.
sort_by string No Field to sort results by. Valid values:id, date_added, date_posted, publication_queue. Default: date_added for unposted repositories, date_posted for posted repositories. When sorting by date_posted, repositories without a publication date (null) will be displayed according to the sorting order.
sort_order string No Order of sorting. Valid values:ASC (ascending), DESC (descending). Default: DESC.
page integer No Page number for pagination (1-based). If not specified along with page_size and limit is 0, all records will be returned without pagination.
page_size integer No Number of items per page. If not specified along with page and limit is 0, all records will be returned without pagination.
text_language string No Optional. When omitted, raw multilingual text is returned in the original format, for example "===(en)text===(uk)text===". When provided (e.g., "en", "uk"), the API extracts and returns only the specified language’s text.
id integer No Address a single repository directly by id. Mutually exclusive with url. When set, sorting, limit and pagination are ignored and the response carries exactly that repository.
url string No Address a single repository directly by url. Mutually exclusive with id. Same semantics as id.

Addressing a single repository:

id and url exist for callers that already know which repository they want — for example content-maestro re-sending a publication that failed for one social connector. Because the item is fetched by identity rather than pulled from the queue, it is returned even when it is already posted.

text_language applies exactly as it does in queue mode, including its fallback: if the repository has no text in the requested language, the response is still 200 and carries the Ukrainian text, or the first language stored, with no indication that a substitution happened. A caller that must publish in one specific language has to compare the text itself.

curl -X POST \
  'http://localhost:8080/think-root/api/get-repository/' \
  -H 'Authorization: Bearer <BEARER_TOKEN>' \
  -H 'Content-Type: application/json' \
  -d '{
    "url": "https://github.com/resemble-ai/chatterbox",
    "text_language": "en"
  }'

Responses: 200 with a single-item payload (page, page_size, total_pages, total_items are all 1; all/posted/unposted still report global counts), 400 when both id and url are given or the identifier is empty/non-positive, 404 when no repository matches.

Request Examples:

  1. Get all records without pagination:
{
  "limit": 0,
  "posted": null,
  "sort_by": "date_added",
  "sort_order": "DESC"
}
  1. Get records with pagination:
{
  "limit": 0,
  "posted": null,
  "sort_by": "date_added",
  "sort_order": "DESC",
  "page": 1,
  "page_size": 10
}
  1. Get limited number of records:
{
  "limit": 5,
  "posted": true,
  "sort_by": "date_posted",
  "sort_order": "DESC"
}
  1. Example: Specifying text_language: 'uk' returns only the Ukrainian text
{
  "limit": 10,
  "text_language": "uk"
}
  1. ** Get English text with pagination:**
{
  "limit": 0,
  "page": 1,
  "page_size": 5,
  "text_language": "en"
}
  1. Example: Without text_language (raw multilingual text)
{
  "limit": 10
}

Returns raw multilingual text segments in the original format, e.g., "===(en)text===(uk)text===".

Pagination Details:

  • When limit is 0:
    • If neither page nor page_size are specified, returns all matching records without pagination. In this case, page=0, page_size=0, total_pages=1, and total_items equals the count of all matching records.
    • If either page or page_size are specified, pagination mode is used.
  • When limit > 0:
    • Pagination mode is used. If page < 1, it defaults to 1. If page_size < 1, it defaults to 10.
  • Effective page size. A page holds limit rows when limit > 0, and page_size rows otherwise. The offset and total_pages are both derived from that effective size, and page_size in the response echoes it. So {"limit": 4, "page_size": 2} returns 4 rows per page and reports page_size: 4 — sending both no longer makes consecutive pages overlap.
  • Response always includes:
    • page: Current page number (0 when returning all records without pagination; otherwise the active page number).
    • page_size: Number of items per page (0 when returning all records without pagination; otherwise the effective page size described above).
    • total_pages: Total number of pages (1 when returning all records without pagination).
    • total_items: Total number of items matching the query.

Sorting Behavior:

  • When sorting by date_posted:
    • If sort_order = ASC: entries with null values are shown first, followed by dates in ascending order
    • If sort_order = DESC: entries with dates are shown in descending order first, followed by those with null values
  • When sorting by publication_queue: promoted unposted repositories are shown first by newest promotion, followed by normal unposted repositories ordered by oldest date_added
  • When sorting by date_added or id: standard ascending or descending sort
  • If sort_by is not specified, date_posted is used for posted=true and date_added for posted=false
  • If sort_order is not specified, DESC is used as default

Response Example:

{
  "status": "ok",
  "message": "Repositories fetched successfully",
  "data": {
    "all": 50,
    "posted": 20,
    "unposted": 30,
    "items": [
      {
        "id": 1,
        "posted": false,
        "url": "https://github.com/example/repo",
        "text": "Repository description here.",
        "date_added": "2025-03-20T15:30:45Z",
        "date_posted": null,
        "publish_priority": null
      }
    ],
    "page": 1,
    "page_size": 10,
    "total_pages": 5,
    "total_items": 50
  }
}

Additional Response Example (raw multilingual text):

{
  "text": "===(en)An open-source project===(uk)Відкритий проект===",
  "... other fields ...": "..."
}

Note: This indicates raw multilingual text as stored.

/api/update-posted/

Endpoint: /think-root/api/update-posted/

Method: PATCH

Description: This endpoint updates the posted status of a repository identified by its URL. Note: when the URL does not exist, the current implementation returns 500 with a generic error rather than 404. Setting posted=true sets date_posted to current time; posted=false clears date_posted. Any status change clears publish_priority per database.UpdatePostedStatusByURL().

Curl Example:

curl -X PATCH \
  'http://localhost:8080/think-root/api/update-posted/' \
  -H 'Authorization: Bearer <BEARER_TOKEN>' \
  -H 'Content-Type: application/json' \
  -d '{
    "url": "https://github.com/example/repo",
    "posted": true
  }'

Request Example:

{
  "url": "https://github.com/example/repo",
  "posted": true
}

Response Example:

{
  "status": "ok",
  "message": "Posted status updated successfully"
}

/api/promote-repository/

Endpoint: /think-root/api/promote-repository/

Method: PATCH

Description: Promotes an unposted repository to the front of the publication queue without changing its historical date_added. The newest promotion wins. Posted repositories cannot be promoted.

Curl Example:

curl -X PATCH \
  'http://localhost:8080/think-root/api/promote-repository/' \
  -H 'Authorization: Bearer <BEARER_TOKEN>' \
  -H 'Content-Type: application/json' \
  -d '{
    "id": 123
  }'

Request Parameters:

Parameter Type Required Description
id integer No Repository ID. Provide either id or url.
url string No Repository URL. Provide either id or url.

Response Example:

{
  "status": "ok",
  "message": "Repository promoted to publish next",
  "data": {
    "id": 123,
    "posted": false,
    "url": "https://github.com/example/repo",
    "text": "Repository description here.",
    "date_added": "2025-03-20T15:30:45Z",
    "date_posted": null,
    "publish_priority": 7
  }
}

Error Responses:

  • 400: invalid request body or identifier
  • 404: repository not found
  • 409: repository is already posted

/api/update-repository-text/

Endpoint: /think-root/api/update-repository-text/

Method: PATCH

Description: Updates the repository text with two modes:

  • Full replace when text_language is omitted.
  • Strict language-specific update when text_language is provided. If the specified language does not exist in the existing multilingual content, returns 422 Unprocessable Entity.

Request Schema:

  • Exactly one of id or url must be provided
  • text is required
  • text_language is optional (language code). When provided, triggers language-specific update.
  • Language code validation is performed by language.ValidateLanguageCodes()

Curl and JSON Examples:

  1. Full replace (no text_language):
curl -X PATCH \
  'http://localhost:8080/think-root/api/update-repository-text/' \
  -H 'Authorization: Bearer <BEARER_TOKEN>' \
  -H 'Content-Type: application/json' \
  -d '{
    "id": 1172,
    "text": "Updated text content via ID"
  }'

Result in DB: "Updated text content via ID" Response includes available_languages (for plain: ["uk"]) and omits updated_language.

  1. Language update on existing multilingual:
curl -X PATCH \
  'http://localhost:8080/think-root/api/update-repository-text/' \
  -H 'Authorization: Bearer <BEARER_TOKEN>' \
  -H 'Content-Type: application/json' \
  -d '{
    "id": 1172,
    "text": "Updated text content via ID",
    "text_language": "en"
  }'

Result in DB: only the en segment updated; other segments unchanged.

  1. Language update on plain existing text:
curl -X PATCH \
  'http://localhost:8080/think-root/api/update-repository-text/' \
  -H 'Authorization: Bearer <BEARER_TOKEN>' \
  -H 'Content-Type: application/json' \
  -d '{
    "id": 1172,
    "text": "тут якийсь текст",
    "text_language": "uk"
  }'

Result in DB: "===(uk)тут якийсь текст==="

  1. Error when language missing in existing multilingual:
curl -X PATCH \
  'http://localhost:8080/think-root/api/update-repository-text/' \
  -H 'Authorization: Bearer <BEARER_TOKEN>' \
  -H 'Content-Type: application/json' \
  -d '{
    "id": 1172,
    "text": "KURWA! Ja perdoly jjajajajaj.",
    "text_language": "pl"
  }'

Response: 422 Unprocessable Entity with message language 'pl' not found in existing content.

Request Parameters:

Parameter Type Required Description
id integer No* Repository ID (positive integer)
url string No* Repository URL (non-empty string)
text string Yes New text content (1-1000 characters, valid UTF-8)
text_language string No Optional language code. When provided, performs a language-specific update (validated).

*Exactly one of id or url must be provided.

Validation Rules:

  • Exactly one identifier (id or url) must be provided
  • text is required and non-empty
  • text length ≤ 1000 characters
  • text must be valid UTF-8
  • text_language validated via language.ValidateLanguageCodes()

Response Fields:

  • status and message
  • data.id, data.url, data.text (final text stored)
  • data.updated_language present only when text_language is provided
  • data.available_languages via multilingual.GetAvailableLanguages()
  • data.updated_at

Success Response Examples:

Full replace (plain text):

{
  "status": "ok",
  "message": "Repository text updated successfully",
  "data": {
    "id": 1172,
    "url": "https://github.com/example/repo",
    "text": "Updated text content via ID",
    "available_languages": ["uk"],
    "updated_at": "2025-06-22T15:00:00Z"
  }
}

Language-specific update:

{
  "status": "ok",
  "message": "Repository text updated successfully",
  "data": {
    "id": 1172,
    "url": "https://github.com/example/repo",
    "text": "Updated text content via ID",
    "updated_language": "en",
    "available_languages": ["en", "uk"],
    "updated_at": "2025-06-22T15:00:00Z"
  }
}

Error Response Example (missing language in existing multilingual):

{
  "status": "error",
  "message": "language 'pl' not found in existing content"
}

/api/delete-repository/

Endpoint: /think-root/api/delete-repository/

Method: DELETE

Description: This endpoint deletes a repository from the database. The repository can be identified by either its unique ID or URL.

Curl Examples:

Delete by ID:

curl -X DELETE \
  'http://localhost:8080/think-root/api/delete-repository/' \
  -H 'Authorization: Bearer <BEARER_TOKEN>' \
  -H 'Content-Type: application/json' \
  -d '{
    "id": 123
  }'

Delete by URL:

curl -X DELETE \
  'http://localhost:8080/think-root/api/delete-repository/' \
  -H 'Authorization: Bearer <BEARER_TOKEN>' \
  -H 'Content-Type: application/json' \
  -d '{
    "url": "https://github.com/example/repo"
  }'

Request Parameters:

Parameter Type Required Description
id integer No* Repository ID (positive integer)
url string No* Repository URL (non-empty string)

*Either id or url must be provided, but not both.

Request Examples:

  1. Delete by ID:
{
  "id": 123
}
  1. Delete by URL:
{
  "url": "https://github.com/example/awesome-project"
}

Validation Rules:

  • Exactly one identifier (id or url) must be provided
  • ID must be a positive integer if provided
  • URL must be a non-empty string if provided

Status Codes:

  • 200: Success - Repository deleted
  • 400: Bad Request - Validation errors
  • 401: Unauthorized - Invalid or missing Bearer token
  • 405: Method Not Allowed - Wrong HTTP method
  • 500: Internal Server Error - Database or server error (including when URL not found in update-posted)

Success Response Example:

{
  "status": "ok",
  "message": "Repository deleted successfully"
}

Error Response Examples:

{
  "status": "error",
  "message": "Either id or url must be provided"
}
{
  "status": "error",
  "message": "Provide either id or url, not both"
}
{
  "status": "error",
  "message": "repository with ID 123 not found"
}
{
  "status": "error",
  "message": "repository with URL https://github.com/example/repo not found"
}

Archive

Archiving moves a published repository out of github_repositories into the append-only archived_repositories table, keeping its original id, description, date_added and date_posted, and stamping date_archived.

Consequences of the move:

  • The repository disappears from /api/get-repository/ and its URL becomes free again, so the very same repository can be collected by /api/manual-generate/ or /api/auto-generate/ and published a second time.
  • URLs are not unique in the archive: the same repository may appear there several times, once per publication cycle.
  • There is no way back: a repository cannot be restored from the archive, and archived rows cannot be deleted. This is deliberate — it keeps the archive free of ambiguity when a repository has been published more than once.
  • Only published repositories (posted = 1) can be archived. Unpublished ones can only be removed with /api/delete-repository/.

/api/archive-repository/

Endpoint: /think-root/api/archive-repository/

Method: POST

Description: Archives one or more published repositories, identified either by ids or by urls. Identifiers that cannot be archived (not found, not published, repeated in the same request) are reported in failed and do not abort the rest of the batch, so the response is 200 even when some or all identifiers were rejected. Implemented by database.ArchiveRepositories().

Curl Examples:

Archive by ids:

curl -X POST \
  'http://localhost:8080/think-root/api/archive-repository/' \
  -H 'Authorization: Bearer <BEARER_TOKEN>' \
  -H 'Content-Type: application/json' \
  -d '{
    "ids": [123, 124]
  }'

Archive by urls:

curl -X POST \
  'http://localhost:8080/think-root/api/archive-repository/' \
  -H 'Authorization: Bearer <BEARER_TOKEN>' \
  -H 'Content-Type: application/json' \
  -d '{
    "urls": ["https://github.com/example/repo"]
  }'

Request Parameters:

Parameter Type Required Description
ids array of integer No* Repository ids (positive integers)
urls array of string No* Repository urls (non-empty strings)

*Exactly one of ids or urls must be provided, non-empty, with at most 100 entries.

Validation Rules:

  • Exactly one of ids or urls must be provided and must not be empty
  • Every id must be a positive integer
  • Every url must be a non-empty string
  • At most 100 identifiers per request

Failure Reasons (per identifier, inside failed):

Reason Meaning
not_found No repository with this id/url exists
not_posted The repository has not been published yet
already_processed The identifier was repeated in the same request

Status Codes:

  • 200: Success - see archived and failed for per-identifier results
  • 400: Bad Request - Validation errors
  • 401: Unauthorized - Invalid or missing Bearer token
  • 405: Method Not Allowed - Wrong HTTP method
  • 500: Internal Server Error - Database or server error

Success Response Example:

{
  "status": "ok",
  "message": "Archived 1 of 2 repositories",
  "data": {
    "archived": [
      {
        "archive_id": 11,
        "id": 123,
        "url": "https://github.com/example/repo",
        "date_added": "2025-03-20T15:30:45Z",
        "date_posted": "2025-03-25T09:00:00Z",
        "date_archived": "2025-05-01T12:00:00Z"
      }
    ],
    "failed": [
      {
        "identifier": "124",
        "reason": "not_posted",
        "message": "only published repositories can be archived"
      }
    ]
  }
}

Error Response Examples:

{
  "status": "error",
  "message": "Either ids or urls must be provided"
}
{
  "status": "error",
  "message": "Provide either ids or urls, not both"
}
{
  "status": "error",
  "message": "A maximum of 100 ids can be archived per request"
}

/api/archive-old-repositories/

Endpoint: /think-root/api/archive-old-repositories/

Method: POST

Description: Archives every published repository whose publication date is older than days days. The age is measured by date_posted, not by date_added; repositories without a publication date are never touched. Use dry_run to preview the affected repositories without changing anything. Implemented by database.ArchiveRepositoriesOlderThan().

Curl Example:

curl -X POST \
  'http://localhost:8080/think-root/api/archive-old-repositories/' \
  -H 'Authorization: Bearer <BEARER_TOKEN>' \
  -H 'Content-Type: application/json' \
  -d '{
    "days": 30,
    "dry_run": false
  }'

Request Parameters:

Parameter Type Required Description
days integer Yes Archive repositories published more than this many days ago. Must be >= 1.
dry_run boolean No When true, returns the matching repositories without archiving them. Default false.

Validation Rules:

  • days must be a positive integer; 0 or a missing value is rejected so nothing can be wiped by accident

Status Codes:

  • 200: Success - Repositories archived (or previewed)
  • 400: Bad Request - Validation errors
  • 401: Unauthorized - Invalid or missing Bearer token
  • 405: Method Not Allowed - Wrong HTTP method
  • 500: Internal Server Error - Database or server error

Success Response Example:

{
  "status": "ok",
  "message": "Archived 2 repositories published more than 30 days ago",
  "data": {
    "archived_count": 2,
    "dry_run": false,
    "archived": [
      {
        "archive_id": 11,
        "id": 123,
        "url": "https://github.com/example/repo",
        "date_added": "2025-01-10T15:30:45Z",
        "date_posted": "2025-01-15T09:00:00Z",
        "date_archived": "2025-05-01T12:00:00Z"
      }
    ]
  }
}

On a dry run archive_id is 0 and date_archived is null, because nothing was written.

Error Response Example:

{
  "status": "error",
  "message": "days must be a positive integer"
}

/api/get-archived-repositories/

Endpoint: /think-root/api/get-archived-repositories/

Method: POST

Description: Returns a paginated, filtered and sorted view of the archive. Supports substring search over url and text, plus independent ranges over all three dates (date_added, date_posted, date_archived). Pagination behaves exactly like /api/get-repository/. Implemented by database.GetArchivedRepositories().

Curl Example:

curl -X POST \
  'http://localhost:8080/think-root/api/get-archived-repositories/' \
  -H 'Authorization: Bearer <BEARER_TOKEN>' \
  -H 'Content-Type: application/json' \
  -d '{
    "page": 1,
    "page_size": 10,
    "url": "example",
    "date_archived_from": "2025-01-01",
    "sort_by": "date_archived",
    "sort_order": "desc",
    "text_language": "uk"
  }'

Request Parameters:

Parameter Type Required Description
limit integer No Maximum number of rows to return. Same semantics as in /api/get-repository/.
page integer No Page number for pagination (1-based).
page_size integer No Number of items per page. Defaults to 10 once pagination is active.
sort_by string No Sort field. Valid values:date_archived, date_posted, date_added, id. Default: date_archived.
sort_order string No asc or desc (case-insensitive). Default: desc.
url string No Case-insensitive substring match on the repository url, Cyrillic included. % and _ are matched literally.
text string No Case-insensitive substring match on the stored description, Cyrillic included.
date_added_from string No Lower bound (inclusive) on date_added. RFC3339 timestamp or YYYY-MM-DD.
date_added_to string No Upper bound (exclusive) on date_added. A bare YYYY-MM-DD covers the whole day.
date_posted_from string No Lower bound (inclusive) on date_posted.
date_posted_to string No Upper bound (exclusive) on date_posted.
date_archived_from string No Lower bound (inclusive) on date_archived.
date_archived_to string No Upper bound (exclusive) on date_archived.
text_language string No When omitted, the raw multilingual text is returned as stored. When provided (e.g. en, uk), only that language is returned.

All filters are combined with AND.

Validation Rules:

  • Dates must be an RFC3339 timestamp (2025-05-01T12:00:00Z) or a bare date (2025-05-01)
  • sort_by must be one of date_archived, date_posted, date_added, id
  • sort_order must be asc or desc
  • limit, page and page_size must not be negative
  • text_language must be a single valid language code (comma-separated lists are rejected)

Sorting Behavior:

  • Rows whose sort column is NULL (possible for date_posted / date_added) are always placed last, in both asc and desc. date_archived is never null.
  • Ties are broken by id in the same direction as the requested order.

Pagination Details:

  • Identical to /api/get-repository/: when neither page, page_size nor limit is set, all matching rows are returned with page=0, page_size=0, total_pages=1. The same effective page size rule applies — a page holds limit rows when limit > 0, and the offset, total_pages and the echoed page_size all follow it.
  • all is the total number of rows in the archive, ignoring filters; total_items is the number of rows matching the current filter.

Status Codes:

  • 200: Success - Archived repositories fetched
  • 400: Bad Request - Validation errors
  • 401: Unauthorized - Invalid or missing Bearer token
  • 405: Method Not Allowed - Wrong HTTP method
  • 500: Internal Server Error - Database or server error

Success Response Example:

{
  "status": "ok",
  "message": "Archived repositories fetched successfully",
  "data": {
    "all": 120,
    "items": [
      {
        "id": 11,
        "original_id": 123,
        "url": "https://github.com/example/repo",
        "text": "Repository description here.",
        "date_added": "2025-03-20T15:30:45Z",
        "date_posted": "2025-03-25T09:00:00Z",
        "date_archived": "2025-05-01T12:00:00Z"
      }
    ],
    "page": 1,
    "page_size": 10,
    "total_pages": 12,
    "total_items": 120
  }
}

id is the archive row id; original_id is the id the repository had in github_repositories before it was archived (it may be reused by a later, unrelated repository).

Error Response Examples:

{
  "status": "error",
  "message": "Invalid date_archived_from: must be an RFC3339 timestamp or a YYYY-MM-DD date"
}
{
  "status": "error",
  "message": "sort_by must be one of: date_archived, date_posted, date_added, id"
}