{"id":23187068,"url":"https://github.com/metastable-void/alarkhabil-server","last_synced_at":"2025-04-05T05:11:14.481Z","repository":{"id":202960077,"uuid":"707764803","full_name":"metastable-void/alarkhabil-server","owner":"metastable-void","description":"Backend API server for Al Arkhabil, the independent thought publication platform.","archived":false,"fork":false,"pushed_at":"2023-11-30T11:10:03.000Z","size":126,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-02-10T13:09:38.591Z","etag":null,"topics":["api","backend","backend-api","cms","cms-backend","rust"],"latest_commit_sha":null,"homepage":"","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/metastable-void.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null}},"created_at":"2023-10-20T15:52:06.000Z","updated_at":"2023-10-24T20:12:10.000Z","dependencies_parsed_at":null,"dependency_job_id":"d72404ee-b072-42f8-aadb-6952208c7191","html_url":"https://github.com/metastable-void/alarkhabil-server","commit_stats":null,"previous_names":["metastable-void/alarkhabil-server"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/metastable-void%2Falarkhabil-server","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/metastable-void%2Falarkhabil-server/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/metastable-void%2Falarkhabil-server/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/metastable-void%2Falarkhabil-server/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/metastable-void","download_url":"https://codeload.github.com/metastable-void/alarkhabil-server/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247289429,"owners_count":20914464,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["api","backend","backend-api","cms","cms-backend","rust"],"created_at":"2024-12-18T10:18:31.819Z","updated_at":"2025-04-05T05:11:14.460Z","avatar_url":"https://github.com/metastable-void.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Al Arkhabil API server\n\nBackend API server for Al Arkhabil, the independent thought publication platform.\n\n* [Frontend code Github](https://github.com/metastable-void/alarkhabil-frontend)\n\n## About this API\n\n* Strings are always in UTF-8.\n* Maximum post size in Markdown is 100kB.\n* Maximum bio/channel description size in Markdown is 4kB.\n* File uploads/user icons are not supported in the first release, but will be supported in future releases.\n* API pagination is not supported first. Dangerous queries are limited to 1000 entries.\n* Body text (including channel/author descriptions) strings are assumed to be in Markdown. Titles and names are not. Currently this server does not parse Markdown.\n\n### Tokens\n\n* __Invite making token__: hex-encoded string of random data. Can be used to create a new invite.\n* __Admin token__: hex-encoded string of random data. Can be used for administrative actions (e.g. deletion of users, etc.).\n* __Invite token__: base64-encoded string containing message signed by the server. Can be used for requesting a new account. Can be parsed freely to get the invited user's UUID.\n\n## List of v1 endpoints\n\nAll endpoints are below `/api/v1/`.\n\nRegex for ValidDnsToken(): `^[a-z0-9]+(-[a-z0-9]+)*$`\n\n* __Account__ means the authenticating user's account.\n* __self__ means the author object of the authenticating user.\n* __Author__ is an author object.\n\nMethod | URL | Auth | Invariant | Input\n-------|-----|------|-----------|------\nGET | invite/new | `{invite making token}` | - | Query: `token`\nPOST | account/new | Self-signed by new public key | Public key does not exist yet on DB | Signed JSON data (POST)\nPOST | account/check_credentials | **Pubkey account auth** | NotDeleted(Account) | Signed JSON data (POST)\nPOST | account/change_credentials | **Pubkey account auth** (Signed by old public key) | NotDeleted(Account) \u0026\u0026 Valid signature by new public key included | Signed JSON data (POST)\nPOST | account/delete | **Pubkey account auth** | NotDeleted(Account) | Signed JSON data (POST)\nPOST | admin/meta/update | `{admin token}` | ValidDnsToken(`page_name`) | Query: `token`; Plain JSON data (POST)\nPOST | admin/meta/delete | `{admin token}` | MetaPageExists(`page_name`) | Query: `token`, `page_name`; Empty POST data\nPOST | admin/author/delete | `{admin token}` | AuthorExists(`uuid`) | Query: `token`, `uuid`; Empty POST data\nPOST | admin/channel/delete | `{admin token}` | ChannelExists(`uuid`) | Query: `token`, `uuid`; Empty POST data\nPOST | admin/post/delete | `{admin token}` | PostExists(`uuid`) | Query: `token`, `uuid`; Empty POST data\nPOST | self/update | **Pubkey account auth** | NotDeleted(Account) | Signed JSON data (POST)\nPOST | channel/new | **Pubkey account auth** | NotDeleted(Account) \u0026\u0026 !ChannelExists(`handle`) \u0026\u0026 ValidDnsToken(`handle`) | Signed JSON data (POST)\nPOST | channel/update | **Pubkey account auth** | NotDeleted(Account) \u0026\u0026 NotDeleted(Channel) \u0026\u0026 Owns(Channel) \u0026\u0026 NoConflict(`handle`) \u0026\u0026 ValidDnsToken(`handle`) | Signed JSON data (POST)\nPOST | channel/delete | **Pubkey account auth** | NotDeleted(Account) \u0026\u0026 NotDeleted(Channel) \u0026\u0026 Owns(Channel) | Signed JSON data (POST)\nPOST | channel/add_author | **Pubkey account auth** | NotDeleted(Account) \u0026\u0026 NotDelete(Channel) \u0026\u0026 Owns (Channel) \u0026\u0026 NotDeleted(Author) \u0026\u0026 Account != Author | Signed JSON data (POST)\nPOST | channel/remove_author | **Pubkey account auth** | NotDeleted(Account) \u0026\u0026 NotDelete(Channel) \u0026\u0026 Owns (Channel) \u0026\u0026 NotDeleted(Author) \u0026\u0026 Account != Author | Signed JSON data (POST)\nPOST | post/new | **Pubkey account auth** | NotDeleted(Account) \u0026\u0026 NotDeleted(Channel) \u0026\u0026 Owns(Channel) | Signed JSON data (POST)\nPOST | post/update | **Pubkey account auth** | NotDeleted(Account) \u0026\u0026 NotDeleted(Channel) \u0026\u0026 NotDeleted(Post) \u0026\u0026 Owns(Channel) | Signed JSON data (POST)\nPOST | post/delete | **Pubkey account auth** | NotDeleted(Account) \u0026\u0026 NotDeleted(Channel) \u0026\u0026 NotDeleted(Post) \u0026\u0026 Owns(Channel) | Signed JSON data (POST)\nGET | meta/info | - | MetaPageExists(`page_name`) | Query: `page_name`\nGET | meta/list | - | - | -\nGET | author/info | - | NotDeleted(Author) | Query: `uuid`\nGET | author/list | - | NotDeleted(Author) | -\nGET | author/channels | - | NotDeleted(Author) \u0026\u0026 NotDeleted(Channel) | Query: `uuid`\nGET | author/posts | - | NotDeleted(Author) \u0026\u0026 NotDeleted(Channel) \u0026\u0026 NotDeleted(Post) \u0026\u0026 NotDeleted(Revision) | Query: `uuid`\nGET | channel/info | - | NotDeleted(Channel) | Query: `uuid` or `handle`\nGET | channel/list | - | NotDeleted(Channel) | -\nGET | channel/authors | - | NotDeleted(Channel) \u0026\u0026 NotDeleted(Author) | Query: `uuid`\nGET | channel/posts | - | NotDeleted(Channel) \u0026\u0026 NotDeleted(Post) | Query: `uuid`\nGET | post/info | - | NotDeleted(Post) \u0026\u0026 NotDeleted(Channel) [ \u0026\u0026 HasUndeleted(Revision) ] | Query: `uuid`\nGET | post/list | - | NotDeleted(Post) \u0026\u0026 NotDeleted(Channel) [ \u0026\u0026 HasUndeleted(Revision) ] | -\nGET | tag/list | - | NotDeleted(Post) \u0026\u0026 NotDeleted(Channel) \u0026\u0026 HasUndeleted(Revision) | -\nGET | tag/posts | - | NotDeleted(Post) \u0026\u0026 NotDeleted(Channel) \u0026\u0026 HasUndeleted(Revision) | -\n\n## Invites v1\n\n### GET /api/v1/invite/new\n\nThe administrator would have access to their *invite making token*.\n\nThe administrator uses the *invite making token* to make a request of this type, and they will get an invite token in base64, which they can tell someone.\n\n**Note:** This endpoint uses GET method because it does not change the state on the server (in the first design).\n\n**Query format:** `?token={invite making token}`\n\n**Response type:** JSON\n\nWill return **400 Bad Request** for invalid requests.\n\nResponse:\n\n```\nHTTP/1.1 200\n{\n    \"status\": \"ok\",\n    \"invite\": \"\u003cinvite token string (base64)\u003e\"\n}\n```\n\n## Accounts v1\n\n### POST /api/v1/account/new\n\nThe user who wants to create an account, creates an ed25519 key pair and signs the folowing payload with the private key.\n\nThe payload contains the new account's name and an invite token from `GET /api/v1/invite/new`.\nThe signed message contains the user's ed25519 public key.\n\nThe response will contain the new account's UUID.\n\n**Post data:** Alarkhabil-ed25519-signed JSON\n\n**Response type:** JSON\n\nWill return **400 Bad Request** for invalid requests.\n\nPayload:\n\n```\n{\n    \"command\": \"account_new\",\n    \"name\": \"\u003cname\u003e\",\n    \"invite\": \"\u003cinvite token string (base64)\u003e\"\n}\n```\n\nResponse example:\n\n```\nHTTP/1.1 200\n{\n    \"status\": \"ok\",\n    \"uuid\": \"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx\"\n}\n```\n\n### POST /api/v1/account/check_credentials\n\nBasically a no-op authenticated request. Returns an error if authentication fails.\n\n**Post data:** Alarkhabil-ed25519-signed JSON\n\n**Response type:** JSON\n\nWill return **400 Bad Request** for invalid requests.\n\nPayload:\n\n```\n{\n    \"command\": \"account_check_credentials\"\n}\n```\n\nResponse example:\n\n```\nHTTP/1.1 200\n{\n    \"status\": \"ok\"\n}\n```\n\n### POST /api/v1/account/change_credentials\n\n**Post data:** Alarkhabil-ed25519-signed JSON\n\n**Response type:** JSON\n\nWill return **400 Bad Request** for invalid requests.\n\nPayload:\n\n```\n{\n    \"command\": \"account_change_credentials\",\n    \"new_algo\": \"\u003cnew public key's algorithm\u003e\",\n    \"new_public_key\": \"\u003cbase64-encoded new public key\u003e\",\n    \"signature\": \"\u003cbase64-encoded signature for old public key binary data by new public key\u003e\"\n}\n```\n\nResponse example:\n\n```\nHTTP/1.1 200\n{\n    \"status\": \"ok\"\n}\n```\n\n### POST /api/v1/account/delete\n\n**TODO: Is this really needed?**\n\n**Post data:** Alarkhabil-ed25519-signed JSON\n\n**Response type:** JSON\n\nWill return **400 Bad Request** for invalid requests.\n\nPayload:\n\n```\n{\n    \"command\": \"account_delete\"\n}\n```\n\nResponse example:\n\n```\nHTTP/1.1 200\n{\n    \"status\": \"ok\"\n}\n```\n\n## Admin v1\n\n### POST /api/v1/admin/meta/update\n\nCreates or updates a meta page.\n\n**Query format:** `?token={admin token}`\n\n**Post data:** JSON\n\n**Response type:** JSON\n\nWill return **400 Bad Request** for invalid requests.\n\nRequest:\n\n```\n{\n    \"page_name\": \"\u003cname of meta page (part of url)\u003e\",\n    \"title\": \"\u003ctitle of meta page\u003e\",\n    \"text\": \"\u003cmarkdown text of meta page\u003e\"\n}\n```\n\nResponse:\n\n```\nHTTP/1.1 200\n{\n    \"status\": \"ok\"\n}\n```\n\n### POST /api/v1/admin/meta/delete\n\nDeletes a meta page. This is irreversible.\n\n**Query format:** `?token={admin token}\u0026page_name={page name}`\n\n**Post data:** none\n\n**Response type:** JSON\n\nWill return **400 Bad Request** for invalid requests.\n\nResponse:\n\n```\nHTTP/1.1 200\n{\n    \"status\": \"ok\"\n}\n```\n\n### POST /api/v1/admin/author/delete\n\n**Query format:** `?token={admin token}\u0026uuid={author's uuid}`\n\n**Post data:** none\n\n**Response type:** JSON\n\nWill return **400 Bad Request** for invalid requests.\n\nResponse:\n\n```\nHTTP/1.1 200\n{\n    \"status\": \"ok\"\n}\n```\n\n### POST /api/v1/admin/channel/delete\n\n**Query format:** `?token={admin token}\u0026uuid={channel's uuid}`\n\n**Post data:** none\n\n**Response type:** JSON\n\nWill return **400 Bad Request** for invalid requests.\n\nResponse:\n\n```\nHTTP/1.1 200\n{\n    \"status\": \"ok\"\n}\n```\n\n### POST /api/v1/admin/post/delete\n\n**Query format:** `?token={admin token}\u0026uuid={post's uuid}`\n\n**Post data:** none\n\n**Response type:** JSON\n\nWill return **400 Bad Request** for invalid requests.\n\nResponse:\n\n```\nHTTP/1.1 200\n{\n    \"status\": \"ok\"\n}\n```\n\n## Authors' endpoints v1\n\n### POST /api/v1/self/update\n\n**Post data:** Alarkhabil-ed25519-signed JSON\n\n**Response type:** JSON\n\nWill return **400 Bad Request** for invalid requests.\n\nPayload:\n\n```\n{\n    \"command\": \"self_update\",\n    \"name\": \"\u003cnew name\u003e\",\n    \"description_text\": \"\u003cnew description markdown\u003e\"\n}\n```\n\nResponse example (same as `/api/v1/author/info`):\n\n```\nHTTP/1.1 200\n{\n    \"uuid\": \"\u003cauthor's uuid\u003e\",\n    \"name\": \"\u003cauthor's name\u003e\",\n    \"created_date\": \u003cregistration date in seconds since UNIX epoch (integer)\u003e\n    \"description_text\": \"\u003cdescription markdown\u003e\"\n}\n```\n\n### POST /api/v1/channel/new\n\n**Post data:** Alarkhabil-ed25519-signed JSON\n\n**Response type:** JSON\n\nWill return **400 Bad Request** for invalid requests.\n\nPayload:\n\n```\n{\n    \"command\": \"channel_new\",\n    \"handle\": \"\u003cchannel's handle\u003e\",\n    \"name\": \"\u003cchannel's name\u003e\",\n    \"lang\": \"\u003cchannel's language code\u003e\"\n}\n```\n\nResponse example (same as `/api/v1/channel/info`):\n\n```\nHTTP/1.1 200\n{\n    \"uuid\": \"\u003cchannel's uuid\u003e\",\n    \"handle\": \"\u003cchannel's handle\u003e\",\n    \"name\": \"\u003cchannel name\u003e\",\n    \"created_date\": \u003cseconds since UNIX epoch (integer)\u003e\n    \"lang\": \"\u003cchannel's language code\u003e\",\n    \"description_text\": \"\u003cdescription markdown\u003e\"\n}\n```\n\n### POST /api/v1/channel/update\n\n**Post data:** Alarkhabil-ed25519-signed JSON\n\n**Response type:** JSON\n\nWill return **400 Bad Request** for invalid requests.\n\nPayload:\n\n```\n{\n    \"command\": \"channel_update\",\n    \"uuid\": \"\u003cchannel's uuid\u003e\",\n    \"handle\": \"\u003cchannel's new handle\u003e\",\n    \"name\": \"\u003cchannel's new name\u003e\",\n    \"lang\": \"\u003cchannel's new language code\u003e\",\n    \"description_text\": \"\u003cnew description markdown\u003e\"\n}\n```\n\nResponse example (same as `/api/v1/channel/info`):\n\n```\nHTTP/1.1 200\n{\n    \"uuid\": \"\u003cchannel's uuid\u003e\",\n    \"handle\": \"\u003cchannel's handle\u003e\",\n    \"name\": \"\u003cchannel name\u003e\",\n    \"created_date\": \u003cseconds since UNIX epoch (integer)\u003e\n    \"lang\": \"\u003cchannel's language code\u003e\",\n    \"description_text\": \"\u003cdescription markdown\u003e\"\n}\n```\n\n### POST /api/v1/channel/delete\n\n**Post data:** Alarkhabil-ed25519-signed JSON\n\n**Response type:** JSON\n\nWill return **400 Bad Request** for invalid requests.\n\nPayload:\n\n```\n{\n    \"command\": \"channel_delete\",\n    \"uuid\": \"\u003cchannel's uuid\u003e\"\n}\n```\n\nResponse example:\n\n```\nHTTP/1.1 200\n{\n    \"status\": \"ok\"\n}\n```\n\n### POST /api/v1/channel/add_author\n\nYou cannot add yourself as an author.\n\n**Post data:** Alarkhabil-ed25519-signed JSON\n\n**Response type:** JSON\n\nWill return **400 Bad Request** for invalid requests.\n\nPayload:\n\n```\n{\n    \"command\": \"channel_add_author\",\n    \"uuid\": \"\u003cchannel's uuid\u003e\",\n    \"author_uuid\": \"\u003cauthor's uuid\u003e\"\n}\n```\n\nResponse example:\n\n```\nHTTP/1.1 200\n{\n    \"status\": \"ok\"\n}\n```\n\n### POST /api/v1/channel/remove_author\n\nYou cannot remove yourself from a channel.\n\n**Post data:** Alarkhabil-ed25519-signed JSON\n\n**Response type:** JSON\n\nWill return **400 Bad Request** for invalid requests.\n\nPayload:\n\n```\n{\n    \"command\": \"channel_remove_author\",\n    \"uuid\": \"\u003cchannel's uuid\u003e\",\n    \"author_uuid\": \"\u003cauthor's uuid\u003e\"\n}\n```\n\nResponse example:\n\n```\nHTTP/1.1 200\n{\n    \"status\": \"ok\"\n}\n```\n\n### POST /api/v1/post/new\n\n**Post data:** Alarkhabil-ed25519-signed JSON\n\n**Response type:** JSON\n\nWill return **400 Bad Request** for invalid requests.\n\nPayload:\n\n```\n{\n    \"command\": \"post_new\",\n    \"channel_uuid\": \"\u003cchannel's uuid\u003e\",\n    \"title\": \"\u003cpost title\u003e\",\n    \"text\": \"\u003cpost markdown text\u003e\",\n    \"tags\": [\n        \"\u003ctag\u003e\",\n        ...\n    ]\n}\n```\n\nResponse example (same as `/api/v1/post/info`):\n\n```\nHTTP/1.1 200\n{\n    \"post_uuid\": \"\u003cposts's uuid\u003e\",\n    \"channel\": {\n        \"uuid\": \"\u003cchannel's uuid\u003e\",\n        \"handle\": \"\u003cchannel's handle\u003e\",\n        \"name\": \"\u003cchannel's name\u003e\",\n        \"lang\": \"\u003cchannel's language code\u003e\"\n    },\n    \"revision_uuid\": \"\u003crevision's uuid\u003e\",\n    \"revision_date\": \u003crevision date in seconds since UNIX epoch\u003e,\n    \"title\": \"\u003ctitle\u003e\",\n    \"author\": {\n        \"uuid\": \"\u003cauthor's uuid\u003e\",\n        \"name\": \"\u003cauthor's name\u003e\"\n    },\n    \"revision_text\": \"\u003crevision text\u003e\",\n    \"tags\": [\n        \"\u003ctag\u003e\",\n        ...\n    ]\n}\n```\n\n### POST /api/v1/post/update\n\n**Post data:** Alarkhabil-ed25519-signed JSON\n\n**Response type:** JSON\n\nWill return **400 Bad Request** for invalid requests.\n\nPayload:\n\n```\n{\n    \"command\": \"post_update\",\n    \"uuid\": \"\u003cpost's uuid\u003e\",\n    \"title\": \"\u003cnew post title\u003e\",\n    \"text\": \"\u003cnew post markdown text\u003e\",\n    \"tags\": [\n        \"\u003ctag\u003e\",\n        ...\n    ]\n}\n```\n\nResponse example (same as `/api/v1/post/info`):\n\n```\nHTTP/1.1 200\n{\n    \"post_uuid\": \"\u003cposts's uuid\u003e\",\n    \"channel\": {\n        \"uuid\": \"\u003cchannel's uuid\u003e\",\n        \"handle\": \"\u003cchannel's handle\u003e\",\n        \"name\": \"\u003cchannel's name\u003e\",\n        \"lang\": \"\u003cchannel's language code\u003e\"\n    },\n    \"revision_uuid\": \"\u003crevision's uuid\u003e\",\n    \"revision_date\": \"\u003crevision date in seconds since UNIX epoch\u003e\",\n    \"title\": \"\u003ctitle\u003e\",\n    \"author\": {\n        \"uuid\": \"\u003cauthor's uuid\u003e\",\n        \"name\": \"\u003cauthor's name\u003e\"\n    },\n    \"revision_text\": \"\u003crevision text\u003e\",\n    \"tags\": [\n        \"\u003ctag\u003e\",\n        ...\n    ]\n}\n```\n\n### POST /api/v1/post/delete\n\n**Post data:** Alarkhabil-ed25519-signed JSON\n\n**Response type:** JSON\n\nWill return **400 Bad Request** for invalid requests.\n\nPayload:\n\n```\n{\n    \"command\": \"post_delete\",\n    \"uuid\": \"\u003cpost's uuid\u003e\"\n}\n```\n\nResponse example:\n\n```\nHTTP/1.1 200\n{\n    \"status\": \"ok\"\n}\n```\n\n## Public endpoints v1\n\n### GET /api/v1/meta/info\n\n**Query format:** `?page_name={meta page name}`\n\n**Response type:** JSON\n\nResponse (post found):\n\n```\nHTTP/1.1 200\n{\n    \"page_name\": \"\u003cname of meta page\u003e\",\n    \"updated_date\": \u003crevision date in seconds since UNIX epoch\u003e,\n    \"title\": \"\u003ctitle\u003e\",\n    \"text\": \"\u003cpage markdown text\u003e\"\n}\n```\n\nResponse (post not found):\n\n```\nHTTP/1.1 404\n{\n    \"status\": \"not found\"\n}\n```\n\n### GET /api/v1/meta/list\n\n**Query format:** (none) - TODO: allow paging\n\n**Response type:** JSON\n\nResponse:\n\n```\nHTTP/1.1 200\n[\n    {\n        \"page_name\": \"\u003cname of meta page\u003e\",\n        \"updated_date\": \u003crevision date in seconds since UNIX epoch\u003e,\n        \"title\": \"\u003ctitle\u003e\"\n    },\n    ...\n]\n```\n\n### GET /api/v1/author/info\n\n**Query format:** `?uuid={author uuid}`\n\n**Response type:** JSON\n\nResponse (author found):\n\n```\nHTTP/1.1 200\n{\n    \"uuid\": \"\u003cauthor's uuid\u003e\",\n    \"name\": \"\u003cauthor's name\u003e\",\n    \"created_date\": \u003cregistration date in seconds since UNIX epoch (integer)\u003e\n    \"description_text\": \"\u003cdescription markdown\u003e\"\n}\n```\n\nResponse (author not found or deleted):\n\n```\nHTTP/1.1 404\n{\n    \"status\": \"not found\"\n}\n```\n\n### GET /api/v1/author/list\n\nThe results are ordered with the newest registration first.\n\n**Query format:** (none) - TODO: allow paging\n\n**Response type:** JSON\n\nResponse:\n\n```\nHTTP/1.1 200\n[\n    {\n        \"uuid\": \"\u003cauthor's uuid\u003e\",\n        \"name\": \"\u003cauthor's name\u003e\"\n    },\n    ...\n]\n```\n\n### GET /api/v1/author/channels\n\n**Query format:** `?uuid={author uuid}`\n\n**Response type:** JSON\n\nEmpty array will be returned if no channels are found.\n\nResponse (author found):\n\n```\nHTTP/1.1 200\n[\n    {\n        \"uuid\": \"\u003cchannel's uuid\u003e\",\n        \"handle\": \"\u003cchannel's handle\u003e\",\n        \"name\": \"\u003cchannel's name\u003e\",\n        \"lang\": \"\u003cchannel's language code\u003e\"\n    },\n    ...\n]\n```\n\nResponse (author not found or deleted):\n\n```\nHTTP/1.1 404\n{\n    \"status\": \"not found\"\n}\n```\n\n### GET /api/v1/author/posts\n\n**Query format:** `?uuid={author uuid}`\n\n**Response type:** JSON\n\nEmpty array will be returned if no posts are found.\n\nResponse (author found):\n\n```\nHTTP/1.1 200\n[\n    {\n        \"post_uuid\": \"\u003cposts's uuid\u003e\",\n        \"revision_uuid\": \"\u003crevision's uuid\u003e\",\n        \"revision_date\": \"\u003crevision date in seconds since UNIX epoch\u003e\",\n        \"title\": \"\u003ctitle\u003e\",\n        \"channel\": {\n            \"uuid\": \"\u003cchannel's uuid\u003e\",\n            \"handle\": \"\u003cchannel's handle\u003e\",\n            \"name\": \"\u003cchannel's name\u003e\",\n            \"lang\": \"\u003cchannel's language code\u003e\"\n        }\n    },\n    ...\n]\n```\n\nResponse (author not found or deleted):\n\n```\nHTTP/1.1 404\n{\n    \"status\": \"not found\"\n}\n```\n\n### GET /api/v1/channel/info\n\n**Query format:** `?uuid={channel uuid}`\n\n**Query format:** `?handle={channel handle}`\n\n**Response type:** JSON\n\nResponse (channel found):\n\n```\nHTTP/1.1 200\n{\n    \"uuid\": \"\u003cchannel's uuid\u003e\",\n    \"handle\": \"\u003cchannel's handle\u003e\",\n    \"name\": \"\u003cchannel name\u003e\",\n    \"created_date\": \u003cseconds since UNIX epoch (integer)\u003e\n    \"lang\": \"\u003cchannel's language code\u003e\",\n    \"description_text\": \"\u003cdescription markdown\u003e\"\n}\n```\n\nResponse (channel not found or deleted):\n\n```\nHTTP/1.1 404\n{\n    \"status\": \"not found\"\n}\n```\n\n### GET /api/v1/channel/list\n\nThe results are ordered with the newest channel first.\n\n**Query format:** (none) - TODO: allow paging\n\n**Response type:** JSON\n\nResponse:\n\n```\nHTTP/1.1 200\n[\n    {\n        \"uuid\": \"\u003cchannel's uuid\u003e\",\n        \"handle\": \"\u003cchannel's handle\u003e\",\n        \"name\": \"\u003cchannel's name\u003e\",\n        \"lang\": \"\u003cchannel's language code\u003e\"\n    },\n    ...\n]\n```\n\n### GET /api/v1/channel/authors\n\n**Query format:** `?uuid={channel uuid}`\n\n**Response type:** JSON\n\nResponse (channel found):\n\n```\nHTTP/1.1 200\n[\n    {\n        \"uuid\": \"\u003cauthor's uuid\u003e\",\n        \"name\": \"\u003cauthor's name\u003e\"\n    },\n    ...\n]\n```\n\nResponse (channel not found or deleted):\n\n```\nHTTP/1.1 404\n{\n    \"status\": \"not found\"\n}\n```\n\n### GET /api/v1/channel/posts\n\n**Query format:** `?uuid={channel uuid}`\n\n**Response type:** JSON\n\nResponse (channel found):\n\nEmpty array will be returned if no posts are found.\n\n```\nHTTP/1.1 200\n[\n    {\n        \"post_uuid\": \"\u003cposts's uuid\u003e\",\n        \"revision_uuid\": \"\u003crevision's uuid\u003e\",\n        \"revision_date\": \"\u003crevision date in seconds since UNIX epoch\u003e\",\n        \"title\": \"\u003ctitle\u003e\",\n        \"author\": {\n            \"uuid\": \"\u003cauthor's uuid\u003e\",\n            \"name\": \"\u003cauthor's name\u003e\"\n        }\n    },\n    ...\n]\n```\n\nResponse (channel not found or deleted):\n\n```\nHTTP/1.1 404\n{\n    \"status\": \"not found\"\n}\n```\n\n### GET /api/v1/post/info\n\n**Query format:** `?uuid={post uuid}`\n\n**Response type:** JSON\n\nResponse (post found):\n\n```\nHTTP/1.1 200\n{\n    \"post_uuid\": \"\u003cposts's uuid\u003e\",\n    \"channel\": {\n        \"uuid\": \"\u003cchannel's uuid\u003e\",\n        \"handle\": \"\u003cchannel's handle\u003e\",\n        \"name\": \"\u003cchannel's name\u003e\",\n        \"lang\": \"\u003cchannel's language code\u003e\"\n    },\n    \"revision_uuid\": \"\u003crevision's uuid\u003e\",\n    \"revision_date\": \"\u003crevision date in seconds since UNIX epoch\u003e\",\n    \"title\": \"\u003ctitle\u003e\",\n    \"author\": {\n        \"uuid\": \"\u003cauthor's uuid\u003e\",\n        \"name\": \"\u003cauthor's name\u003e\"\n    },\n    \"revision_text\": \"\u003crevision text\u003e\",\n    \"tags\": [\n        \"\u003ctag\u003e\",\n        ...\n    ]\n}\n```\n\nResponse (post not found or deleted):\n\n```\nHTTP/1.1 404\n{\n    \"status\": \"not found\"\n}\n```\n\n### GET /api/v1/post/list\n\nThe results are ordered with the newest post first.\n\n**Query format:** (none) - TODO: allow paging\n\n**Response type:** JSON\n\nResponse:\n\n```\nHTTP/1.1 200\n[\n    {\n        \"post_uuid\": \"\u003cposts's uuid\u003e\",\n        \"revision_uuid\": \"\u003crevision's uuid\u003e\",\n        \"revision_date\": \"\u003crevision date in seconds since UNIX epoch\u003e\",\n        \"title\": \"\u003ctitle\u003e\",\n        \"author\": {\n            \"uuid\": \"\u003cauthor's uuid\u003e\",\n            \"name\": \"\u003cauthor's name\u003e\"\n        },\n        \"channel\": {\n            \"uuid\": \"\u003cchannel's uuid\u003e\",\n            \"handle\": \"\u003cchannel's handle\u003e\",\n            \"name\": \"\u003cchannel's name\u003e\",\n            \"lang\": \"\u003cchannel's language code\u003e\"\n        }\n    },\n    ...\n]\n```\n\n### GET /api/v1/tag/list\n\n**Query format:** (none) - TODO: allow paging\n\n**Response type:** JSON\n\nResponse:\n\n```\nHTTP/1.1 200\n[\n    {\n        \"tag_name\": \"\u003ctag name\u003e\",\n        \"page_count\": \u003cpage count\u003e\n    },\n    ...\n]\n```\n\n### GET /api/v1/tag/posts\n\nThe results are ordered with the newest post first.\n\n**Query format:** `?tag_name={tag name}` - TODO: allow paging\n\n**Response type:** JSON\n\nResponse:\n\n```\nHTTP/1.1 200\n[\n    {\n        \"post_uuid\": \"\u003cposts's uuid\u003e\",\n        \"revision_uuid\": \"\u003crevision's uuid\u003e\",\n        \"revision_date\": \"\u003crevision date in seconds since UNIX epoch\u003e\",\n        \"title\": \"\u003ctitle\u003e\",\n        \"author\": {\n            \"uuid\": \"\u003cauthor's uuid\u003e\",\n            \"name\": \"\u003cauthor's name\u003e\"\n        },\n        \"channel\": {\n            \"uuid\": \"\u003cchannel's uuid\u003e\",\n            \"handle\": \"\u003cchannel's handle\u003e\",\n            \"name\": \"\u003cchannel's name\u003e\",\n            \"lang\": \"\u003cchannel's language code\u003e\"\n        }\n    },\n    ...\n]\n```\n\n## Build\n\n```\ncargo build\n```\n\n## Configuration\n\n```\ncp ./example.env ./.env\n# edit ./.env\n```\n\n## License\n\nLicensed under the Apache 2.0 license.\n\n### Authors\n\n- [@metastable-void](https://github.com/metastable-void)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmetastable-void%2Falarkhabil-server","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmetastable-void%2Falarkhabil-server","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmetastable-void%2Falarkhabil-server/lists"}