{"id":36628687,"url":"https://github.com/radusalagean/open-live-trivia-api","last_synced_at":"2026-01-12T09:35:00.665Z","repository":{"id":134830544,"uuid":"194019933","full_name":"radusalagean/open-live-trivia-api","owner":"radusalagean","description":"Server-side part of the Open Live Trivia project","archived":false,"fork":false,"pushed_at":"2025-12-17T10:29:36.000Z","size":2310,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-12-20T23:25:54.560Z","etag":null,"topics":["express","firebase-admin","mongoose","nodejs","socket-io"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","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/radusalagean.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,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2019-06-27T03:59:32.000Z","updated_at":"2025-12-17T10:29:34.000Z","dependencies_parsed_at":null,"dependency_job_id":"04591173-1e34-4e4e-8cd9-03c730f59746","html_url":"https://github.com/radusalagean/open-live-trivia-api","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/radusalagean/open-live-trivia-api","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/radusalagean%2Fopen-live-trivia-api","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/radusalagean%2Fopen-live-trivia-api/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/radusalagean%2Fopen-live-trivia-api/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/radusalagean%2Fopen-live-trivia-api/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/radusalagean","download_url":"https://codeload.github.com/radusalagean/open-live-trivia-api/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/radusalagean%2Fopen-live-trivia-api/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28337728,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-12T06:09:07.588Z","status":"ssl_error","status_checked_at":"2026-01-12T06:05:18.301Z","response_time":98,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"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":["express","firebase-admin","mongoose","nodejs","socket-io"],"created_at":"2026-01-12T09:35:00.571Z","updated_at":"2026-01-12T09:35:00.649Z","avatar_url":"https://github.com/radusalagean.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# open-live-trivia-api\n**Open Live Trivia** is an open-source multiplayer trivia game. This repo hosts the server-side part of the project. For the corresponding client app, please check [this](https://github.com/radusalagean/open-live-trivia-app-android) link.\n\n## Required files\n```\n~/.open-live-trivia_vault/open-live-trivia-firebase-adminsdk-e5krk-044318ee08.json\nsecrets/db_root_password.txt\n```\n\n## Overview\n### Game rules\n- An entry is displayed every round\n- Every player has 3 free attempts available to submit the correct answer\n- Additional attempts will cost 1 point\n- The answers are case insensitive\n- Every 5 seconds, a new random character is revealed from the answer (defined as a split)\n- Entries range from 10 to 100 points in value (based on their difficulty)\n- Their value decreases as more characters are revealed\n- The first player to submit the correct answer wins the prize and the round is over\n\n### Other features\n- Users are able to request the game leaderboard\n- There are 3 right levels that a user can have:\n  - *Regular* - type `0`\n  - *Moderator* - type `1`\n  - *Admin* - type `2`\n- Users are able to report entries which they consider invalid / inappropriate while playing. Those can be reviewed later and then banned / dismissed by *moderators* or the *admin*.\n- The *admin* is the only one able to grant or revoke extra rights to users\n- All registered users are able to delete their accounts permanently\n\n![Use Case Diagram](diagrams/use-case.png)\n\n## Under the hood\n### Authentication\nPlayer authentication is achieved through [Firebase Auth](https://firebase.google.com/docs/auth/). The client app will send a token to the server that is valid for **one hour**. That token is automatically refreshed by the client app when needed, so tokens sent to the server should always be up to date. Upon receiving an authorized request from the client app, the server sends that token through [Firebase Admin](https://www.npmjs.com/package/firebase-admin) in order to receive a decrypted payload that contains necessary info such as `uid` (an unique id associated with the Google Account used to authenticate) and the `exp` field (the expiration date of the currently used token). The following activity diagram showcases the authentication flow:\n\n![Activity Diagram for Authentication](diagrams/authentication-activity.png)\n[View the full resolution version](https://raw.githubusercontent.com/radusalagean/open-live-trivia-api/master/diagrams/authentication-activity.png)\n\n### Game\nThe communication of game-specific events between the server and the client is based on [socket.io](https://socket.io). This approach facilitates real-time bidirectional communication between the two systems, which is ideal for the purpose of an online multiplayer game. The following activity diagram showcases the flow of game-specific events between the client and the server, based on the rules mentioned above:\n\n![Activity Diagram for the Game](diagrams/game-activity.png)\n[View the full resolution version](https://raw.githubusercontent.com/radusalagean/open-live-trivia-api/master/diagrams/game-activity.png)\n\n## Usage\nBase URL: `https://openlivetrivia.com/api/v1/`\n\nFor requests marked as `🔒`, you need to have the `Authorization` header set with your Firebase `idToken`.\n\nFor requests marked as `🗐`, the results will be paginated and pagination-specific query string can be passed to specify the `page` number. Example: `https://openlivetrivia.com/api/v1/user/leaderboard?page=2`.\n\nFor all POST / PUT requests that have a json body provided, you need to set the `application/json` value for the `Content-Type` header.\n\nIn the documentation, certain attributes displayed with a colon in the beginning (e.g. `:id`) need to be replaced with a corresponding value when you are making the call.\n\n**Profile image access url:**\n`https://openlivetrivia.com/public/user-thumbnails/:filename`, where:\n- `filename` = `userId` + `.png`\n\nExample: `https://openlivetrivia.com/public/user-thumbnails/5d1f77e3adc09e1fe5a9aa9e.png`\n\n## Socket-based events `🔌`\n**Access url:** `https://openlivetrivia.com/api/socket.io`\n### Client `📣` -\u003e `🎧` Server events\n| **Event**| **Description**|\n|----------|----------------|\n| `authentication`|The first event sent by the client after socket connection. Pass the Firebase idToken in order to authenticate.|\n| `ATTEMPT`|An attempt to submit the correct answer for the ongoing round|\n| `REACTION`|An emoji that will broadcast to all the current players|\n| `REPORT_ENTRY`|Report the ongoing entry of this round for further review by moderators or admin|\n| `REQUEST_PLAYER_LIST`|Request the list of currently playing users|\n\n**Note:** Events written in *CAPS* are game-specific events.\n\n- Example bodies:\n  - `authentication`:\n  ```json\n  {\n    \"idToken\": \"YOUR_ID_TOKEN\"\n  }\n  ```\n  - `ATTEMPT`:\n  ```json\n  {\n    \"message\": \"Funcrusher Plus\"\n  }\n  ```\n  - `REACTION`\n  ```json\n  {\n    \"emoji\": \"😇\"\n  }\n  ```\n  **Note:** The events not listed in the examples above don't require request bodies.\n  \n### Server `📣` -\u003e `🎧` Client events\n  |**Event**| **Description**|\n  |---------|----------------|\n  |`authenticated`|Sent to the client which authenticated successfully|\n  |`unauthorized`|Sent to the client which failed to authenticate|\n  |`WELCOME`|The first event sent by the server when a client is connected and authenticated|\n  |`PEER_JOIN`|Broadcasted to all connected clients when a new client is connected and successfully authenticated|\n  |`PEER_ATTEMPT`|Broadcasted to all connected clients when a client sent an attempt to the server|\n  |`INSUFFICIENT_FUNDS`|Sent to the client who previously sent an attempt that he was unable to pay for|\n  |`COIN_DIFF`|Sent to the client who previously sent an attempt if there was a change in his coin bank (like the price paid for the attempt and / or the reward received for the correct answer)|\n  |`PEER_REACTION`|Broadcasted to all connected clients when a client sent a reaction to the server|\n  |`ROUND`|Broadcasted to all connected clients when a new round starts|\n  |`SPLIT`|Broadcasted to all connected clients when a new split starts|\n  |`REVEAL`|Broadcasted to all connected clients when the last split finishes and no one submitted the right answer|\n  |`ENTRY_REPORTED_OK`|Sent to the client who previously reported an entry, if the entry report was saved successfully|\n  |`ENTRY_REPORTED_ERROR`|Sent to the client who previously reported an entry, if the entry report failed to save|\n  |`PLAYER_LIST`|Sent to the client who previously requested a list of all the current players|\n  |`PEER_LEFT`|Broadcasted to all connected clients when a client left the game session|\n  \n  - Examples bodies:\n    - `unauthorized`:\n    ```json\n    {\n      \"message\": \"User id not found for the specified token, you need to register first\"\n    }\n    ```\n    - `WELCOME`\n    ```json\n    {\n      \"gameState\": 1,\n      \"userCoins\": 100,\n      \"entryId\": 121885,\n      \"category\": \"we governed that state\",\n      \"clue\": \"Pat Brown,Pete Wilson\",\n      \"answer\": \"C____o___a\",\n      \"currentValue\": 14,\n      \"elapsedSplitSeconds\": 7,\n      \"totalSplitSeconds\": 15,\n      \"freeAttemptsLeft\": 2,\n      \"entryReported\": false,\n      \"players\": 1,\n      \"attempts\": [\n        {\n          \"userId\": \"5d1f2052a93b8d38b87750d3\",\n          \"username\": \"Radu\",\n          \"message\": \"test attempt\",\n          \"correct\": false\n        }\n      ]\n    }\n    ```\n    - `PEER_JOIN`\n    ```json\n    {\n      \"userId\": \"5d1f2052a93b8d38b87750d3\",\n      \"username\": \"Radu\"\n    }\n    ```\n    - `PEER_ATTEMPT`\n    ```json\n    {\n      \"userId\": \"5d1f2052a93b8d38b87750d3\",\n      \"username\": \"Radu\",\n      \"message\": \"test attempt\",\n      \"correct\": false\n    }\n    ```\n    - `COIN_DIFF`\n    ```json\n    {\n      \"coinDiff\": 6\n    }\n    ```\n    - `PEER_REACTION`\n    ```json\n    {\n      \"userId\": \"5d1f2052a93b8d38b87750d3\",\n      \"username\": \"Radu\",\n      \"emoji\": \"👾\"\n    }\n    ```\n    - `ROUND`\n    ```json\n    {\n      \"entryId\": 121885,\n      \"category\": \"we governed that state\",\n      \"clue\": \"Pat Brown,Pete Wilson\",\n      \"answer\": \"__________\",\n      \"currentValue\": 20\n    }\n    ```\n    - `SPLIT`\n    ```json\n    {\n      \"answer\": \"C_li_ornia\",\n      \"currentValue\": 4\n    }\n    ```\n    - `REVEAL`\n    ```json\n    {\n      \"answer\": \"California\"\n    }\n    ```\n    - `PLAYER_LIST`\n    ```json\n    {\n      \"players\": [\n        {\n          \"_id\": \"5d1f2052a93b8d38b87750d3\",\n          \"username\": \"Radu\",\n          \"rights\": 0,\n          \"coins\": 106,\n          \"joined\": \"2019-07-05T10:41:44.203Z\"\n        }\n      ]\n    }\n    ```\n    - `PEER_LEFT`\n    ```json\n    {\n      \"userId\": \"5d1f2052a93b8d38b87750d3\",\n      \"username\": \"Radu\" \n    }\n    ```\n    \n## Users `👤`\n### Register `🔒`\n**[\u003ccode\u003ePOST\u003c/code\u003e user/register](https://openlivetrivia.com/api/v1/user/register)**\n\nRequest Body Parameters:\n- `username` - *String* (required)\n\nExample Request Body:\n```json\n{\n  \"username\": \"Radu\"\n}\n```\nExample Response Body **`201 CREATED`**:\n```json\n{\n  \"_id\": \"5d1f2052a93b8d38b87750d3\",\n  \"username\": \"Radu\",\n  \"rights\": 0,\n  \"coins\": 100,\n  \"joined\": \"2019-07-05T10:41:44.203Z\"\n}\n```\n\nSpecific restrictions:\n- Max username length: 50 characters\n- Usernames must be unique (otherwise, `409 CONFLICT` will be returned)\n- Usernames are considered unique on a case insensitive basis (e.g. if `Radu` is registered, trying to register `radu` will result in a conflict error)\n- Note: spaces are allowed in usernames\n\n### Login `🔒`\n**[\u003ccode\u003ePOST\u003c/code\u003e user/login](https://openlivetrivia.com/api/v1/user/login)**\n\nExample Response Body **`200 OK`**:\n```json\n{\n  \"_id\": \"5d1f2052a93b8d38b87750d3\",\n  \"username\": \"Radu\",\n  \"rights\": 0,\n  \"coins\": 100,\n  \"joined\": \"2019-07-05T10:41:44.203Z\"\n}\n```\n\n### Delete user `🔒`\n**[\u003ccode\u003eDELETE\u003c/code\u003e user/delete](https://openlivetrivia.com/api/v1/user/delete)**\n\nExample Response Body **`200 OK`**:\n```json\n{\n  \"message\": \"Account removed successfully\"\n}\n```\nSpecific restrictions:\n- Admins are not allowed to remove their accounts\n\n### Username availability\n**[\u003ccode\u003eGET\u003c/code\u003e user/availability/:username](https://openlivetrivia.com/api/v1/user/availability/radu)**\n\nRequest URL parameters:\n- `username` - candidate username (required)\n\nResponse codes: \n- **`200 OK`** - Username is available for registration\n- **`409 CONFLICT`** - Username is unavailable for registration\n\n### Update user rights `🔒 ADMIN`\n**[\u003ccode\u003ePUT\u003c/code\u003e user/rights/:user_id/:rights_level](https://openlivetrivia.com/api/v1//user/rights/5d18a18aa12e471d24085d2e/1)**\n\nRequest URL parameters:\n- `user_id` - the id of the target user (required)\n- `rights_level` - one of the following values: (required)\n  - `0` - *Regular*\n  - `1` - *Moderator*\n  - `2` - *Admin*\n  \nExample Response Body **`200 OK`**:\n```json\n{\n  \"message\": \"Chad's rights changed to type 1\"\n}\n```\n\n### Leaderboard `🔒` `🗐`\n**[\u003ccode\u003eGET\u003c/code\u003e user/leaderboard](https://openlivetrivia.com/api/v1/user/leaderboard)**\n\nExample Response Body **`200 OK`**:\n```json\n{\n  \"page\": 1,\n  \"pages\": 1,\n  \"itemsCount\": 1,\n  \"perPage\": 20,\n  \"items\": [\n    {\n      \"_id\": \"5d1f2968a93b8d38b87750d4\",\n      \"rights\": 2,\n      \"coins\": 100,\n      \"lastSeen\": \"2019-07-05T10:41:44.203Z\",\n      \"joined\": \"2019-07-05T10:41:44.203Z\",\n      \"username\": \"Radu\",\n      \"playing\": true\n    }\n  ]\n}\n```\n\n### Me `🔒`\n**[\u003ccode\u003eGET\u003c/code\u003e user/me](https://openlivetrivia.com/api/v1/user/me)**\n\nExample Response Body **`200 OK`**:\n```json\n{\n  \"_id\": \"5d1f2052a93b8d38b87750d3\",\n  \"username\": \"Radu\",\n  \"rights\": 0,\n  \"coins\": 100,\n  \"joined\": \"2019-07-05T10:41:44.203Z\"\n}\n```\n\n## Entry reports `🚩`\n### Query reports `🔒 MODERATOR / ADMIN` `🗐`\n**[\u003ccode\u003eGET\u003c/code\u003e reported_entry/get_reports](https://openlivetrivia.com/api/v1/reported_entry/get_reports)**\n\nRequest URL query string parameters:\n- `banned` - *Boolean* (optional)\n\nExample Response Body **`200 OK`**:\n```json\n{\n  \"page\": 1,\n  \"pages\": 1,\n  \"itemsCount\": 1,\n  \"perPage\": 10,\n  \"items\": [\n    {\n      \"reporters\": [\n        {\n          \"_id\": \"5d1f2968a93b8d38b87750d4\",\n          \"username\": \"Radu\"\n        }\n      ],\n      \"banned\": false,\n      \"_id\": \"5d1f379a78c6e7342c49488e\",\n      \"lastReported\": \"2019-07-05T11:42:18.216Z\",\n      \"entryId\": 34770,\n      \"category\": \"life science\",\n      \"clue\": \"The monera kingdom consists of bacteria \u0026 the blue-green species of this\",\n      \"answer\": \"Algae\"\n    }\n  ]\n}\n```\n\nNote: Entries currently running in the game will be excluded from the results.\n\n### Ban reported entry `🔒 MODERATOR / ADMIN`\n**[\u003ccode\u003ePUT\u003c/code\u003e reported_entry/ban/:report_id](https://openlivetrivia.com/api/v1/reported_entry/ban/5d1f379a78c6e7342c49488e)**\n\nRequest URL parameters:\n- `report_id` - the id of the target report (required)\n\nExample Response Body **`200 OK`**:\n```json\n{\n  \"message\": \"The entry has been banned successfully\"\n}\n```\n\n### Unban reported entry `🔒 MODERATOR / ADMIN`\n**[\u003ccode\u003ePUT\u003c/code\u003e reported_entry/unban/:report_id](https://openlivetrivia.com/api/v1/reported_entry/unban/5d1f379a78c6e7342c49488e)**\n\nRequest URL parameters:\n- `report_id` - the id of the target report (required)\n\nExample Response Body **`200 OK`**:\n```json\n{\n  \"message\": \"The entry has been unbanned successfully\"\n}\n```\n\n### Dismiss reported entry `🔒 MODERATOR / ADMIN`\n**[\u003ccode\u003ePUT\u003c/code\u003e reported_entry/dismiss/:report_id](https://openlivetrivia.com/api/v1/reported_entry/dismiss/5d1f379a78c6e7342c49488e)**\n\nRequest URL parameters:\n- `report_id` - the id of the target report (required)\n\nExample Response Body **`200 OK`**:\n```json\n{\n  \"message\": \"Entry report dismissed successfully\"\n}\n```\n\n## System `⚙️`\n### Disconnect everyone `🔒 ADMIN`\n**[\u003ccode\u003ePOST\u003c/code\u003e system/disconnect_everyone](https://openlivetrivia.com/api/v1/system/disconnect_everyone)**\n\nExample Response Body **`200 OK`**:\n```json\n{\n  \"message\": \"Sent the disconnect signal to 10 clients\"\n}\n```\n\n### Info\n**[\u003ccode\u003eGET\u003c/code\u003e system/info](https://openlivetrivia.com/api/v1/system/info)**\n\nExample Response Body **`200 OK`**:\n```json\n{\n  \"serverVersion\": \"1.0.0\",\n  \"minAppVersionCode\": 1,\n  \"latestAppVersionCode\": 1,\n  \"isTriviaServiceRunning\": true\n}\n```\n\n## License\n\nApache License 2.0, see the [LICENSE](LICENSE) file for details.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fradusalagean%2Fopen-live-trivia-api","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fradusalagean%2Fopen-live-trivia-api","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fradusalagean%2Fopen-live-trivia-api/lists"}