# Community routes

Star ratings and comments on games (ADR 0012)

Star ratings and comments on games (ADR 0012). Generated from `openapi.yaml` of games-api 0.7.0: 7 routes.

- [`GET /v1/sdk/games/{gameId}/review`](#get-v1-sdk-games-gameid-review): The player's own rating and comments on a game
- [`PUT /v1/sdk/games/{gameId}/rating`](#put-v1-sdk-games-gameid-rating): Rate a game from 1 to 5 stars, or change the rating
- [`DELETE /v1/sdk/games/{gameId}/rating`](#delete-v1-sdk-games-gameid-rating): Remove the player's rating
- [`POST /v1/sdk/games/{gameId}/comments`](#post-v1-sdk-games-gameid-comments): Post a comment on a game
- [`PATCH /v1/sdk/comments/{commentId}`](#patch-v1-sdk-comments-commentid): Change the body of one of the player's comments
- [`DELETE /v1/sdk/comments/{commentId}`](#delete-v1-sdk-comments-commentid): Delete one of the player's comments
- [`GET /v1/catalog/{site}/games/{slug}/reviews`](#get-v1-catalog-site-games-slug-reviews): Rating and visible comments of a game, newest first

Also tagged `community`, documented with their first tag:

- [`GET /v1/admin/sites/{siteId}/comments`](/docs/api/reference/admin/#get-v1-admin-sites-siteid-comments): Comments of a site for moderation, newest first
- [`DELETE /v1/admin/comments/{commentId}`](/docs/api/reference/admin/#delete-v1-admin-comments-commentid): Delete a comment for good
- [`POST /v1/admin/comments/{commentId}/hide`](/docs/api/reference/admin/#post-v1-admin-comments-commentid-hide): Hide a comment from every player route
- [`POST /v1/admin/comments/{commentId}/unhide`](/docs/api/reference/admin/#post-v1-admin-comments-commentid-unhide): Show a hidden comment again
- [`GET /v1/admin/sites/{siteId}/ratings`](/docs/api/reference/admin/#get-v1-admin-sites-siteid-ratings): Rating of every rated game of a site

<RouteHeading id="get-v1-sdk-games-gameid-review" method="GET" path="/v1/sdk/games/{gameId}/review" />

<CodePanel title="GET /v1/sdk/games/{gameId}/review">
  <Fragment slot="curl">

    ```sh
    curl 'https://api.filudi.com/v1/sdk/games/gloop/review' \
      -H "authorization: Bearer $FILUDI_PLAYER_TOKEN"
    ```

  </Fragment>
  <Fragment slot="javascript">

    ```js
    // playerToken: the fp1 token of POST /v1/sdk/players
    const response = await fetch('https://api.filudi.com/v1/sdk/games/gloop/review', {
      headers: { authorization: `Bearer ${playerToken}` },
    });
    const data = await response.json();
    ```

  </Fragment>
  <Fragment slot="python">

    ```py
    import requests

    # player_token: the fp1 token of POST /v1/sdk/players
    response = requests.get(
        'https://api.filudi.com/v1/sdk/games/gloop/review',
        headers={'authorization': f'Bearer {player_token}'},
    )
    response.raise_for_status()
    data = response.json()
    ```

  </Fragment>
</CodePanel>

**The player's own rating and comments on a game**

`gameId` is the game slug, as in `/v1/sdk/saves/{gameId}`. Hidden comments are left out.
Comments are newest first, all of them (a player posts 5 per hour at most).

Credential: a player token (`fp1`), sent as `Authorization: Bearer <token>`. A game token (`fg1`) answers 401 here.

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `gameId` | path | string | yes | Game slug |

### Responses

| Status | Body | Description |
| --- | --- | --- |
| 200 | [PlayerReview](/docs/api/reference/schemas/#schema-playerreview) | Rating and comments. |
| 401 | [Error](/docs/api/reference/schemas/#schema-error) | Missing or invalid token. |
| 404 | [Error](/docs/api/reference/schemas/#schema-error) | Unknown resource. |

<RouteHeading id="put-v1-sdk-games-gameid-rating" method="PUT" path="/v1/sdk/games/{gameId}/rating" />

<CodePanel title="PUT /v1/sdk/games/{gameId}/rating">
  <Fragment slot="curl">

    ```sh
    curl -X PUT 'https://api.filudi.com/v1/sdk/games/gloop/rating' \
      -H "authorization: Bearer $FILUDI_PLAYER_TOKEN" \
      -H 'content-type: application/json' \
      -d '{
      "stars": 1
    }'
    ```

  </Fragment>
  <Fragment slot="javascript">

    ```js
    // playerToken: the fp1 token of POST /v1/sdk/players
    const response = await fetch('https://api.filudi.com/v1/sdk/games/gloop/rating', {
      method: 'PUT',
      headers: { authorization: `Bearer ${playerToken}`, 'content-type': 'application/json' },
      body: JSON.stringify({
        stars: 1,
      }),
    });
    const data = await response.json();
    ```

  </Fragment>
  <Fragment slot="python">

    ```py
    import requests

    # player_token: the fp1 token of POST /v1/sdk/players
    response = requests.put(
        'https://api.filudi.com/v1/sdk/games/gloop/rating',
        headers={'authorization': f'Bearer {player_token}'},
        json={
            'stars': 1,
        },
    )
    response.raise_for_status()
    data = response.json()
    ```

  </Fragment>
</CodePanel>

**Rate a game from 1 to 5 stars, or change the rating**

Guests and accounts. One rating per player and game on the token's site (ADR 0012). The game must be
in the site catalog and visible. 60 ratings per player per hour. The catalog shows the new average
within its 60-second cache.

Credential: a player token (`fp1`), sent as `Authorization: Bearer <token>`. A game token (`fg1`) answers 401 here.

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `gameId` | path | string | yes | Game slug |

### Request body

`application/json`, required.

| Property | Type | Required | Description |
| --- | --- | --- | --- |
| `stars` | integer | yes | 1 to 5 |

### Responses

| Status | Body | Description |
| --- | --- | --- |
| 200 | [RatingSummary](/docs/api/reference/schemas/#schema-ratingsummary) | The game's rating after this one. |
| 400 | [Error](/docs/api/reference/schemas/#schema-error) | Validation failed. |
| 401 | [Error](/docs/api/reference/schemas/#schema-error) | Missing or invalid token. |
| 404 | none | Game not in this site's catalog. |
| 429 | [Error](/docs/api/reference/schemas/#schema-error) | Too many requests. Headers: `Retry-After`. |
| 503 | none | Redis is down; the rate limit cannot be checked. |

<RouteHeading id="delete-v1-sdk-games-gameid-rating" method="DELETE" path="/v1/sdk/games/{gameId}/rating" />

<CodePanel title="DELETE /v1/sdk/games/{gameId}/rating">
  <Fragment slot="curl">

    ```sh
    curl -X DELETE 'https://api.filudi.com/v1/sdk/games/gloop/rating' \
      -H "authorization: Bearer $FILUDI_PLAYER_TOKEN"
    ```

  </Fragment>
  <Fragment slot="javascript">

    ```js
    // playerToken: the fp1 token of POST /v1/sdk/players
    const response = await fetch('https://api.filudi.com/v1/sdk/games/gloop/rating', {
      method: 'DELETE',
      headers: { authorization: `Bearer ${playerToken}` },
    });
    // No body: check response.ok.
    ```

  </Fragment>
  <Fragment slot="python">

    ```py
    import requests

    # player_token: the fp1 token of POST /v1/sdk/players
    response = requests.delete(
        'https://api.filudi.com/v1/sdk/games/gloop/rating',
        headers={'authorization': f'Bearer {player_token}'},
    )
    response.raise_for_status()
    # No body: the status says it all.
    ```

  </Fragment>
</CodePanel>

**Remove the player's rating**

Credential: a player token (`fp1`), sent as `Authorization: Bearer <token>`. A game token (`fg1`) answers 401 here.

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `gameId` | path | string | yes | Game slug |

### Responses

| Status | Body | Description |
| --- | --- | --- |
| 204 | none | Removed. |
| 401 | [Error](/docs/api/reference/schemas/#schema-error) | Missing or invalid token. |

<RouteHeading id="post-v1-sdk-games-gameid-comments" method="POST" path="/v1/sdk/games/{gameId}/comments" />

<CodePanel title="POST /v1/sdk/games/{gameId}/comments">
  <Fragment slot="curl">

    ```sh
    curl -X POST 'https://api.filudi.com/v1/sdk/games/gloop/comments' \
      -H "authorization: Bearer $FILUDI_PLAYER_TOKEN" \
      -H 'content-type: application/json' \
      -d '{
      "body": "string"
    }'
    ```

  </Fragment>
  <Fragment slot="javascript">

    ```js
    // playerToken: the fp1 token of POST /v1/sdk/players
    const response = await fetch('https://api.filudi.com/v1/sdk/games/gloop/comments', {
      method: 'POST',
      headers: { authorization: `Bearer ${playerToken}`, 'content-type': 'application/json' },
      body: JSON.stringify({
        body: 'string',
      }),
    });
    const data = await response.json();
    ```

  </Fragment>
  <Fragment slot="python">

    ```py
    import requests

    # player_token: the fp1 token of POST /v1/sdk/players
    response = requests.post(
        'https://api.filudi.com/v1/sdk/games/gloop/comments',
        headers={'authorization': f'Bearer {player_token}'},
        json={
            'body': 'string',
        },
    )
    response.raise_for_status()
    data = response.json()
    ```

  </Fragment>
</CodePanel>

**Post a comment on a game**

Account players only (ADR 0012). The comment shows at once. The API converts CRLF to LF, removes
control characters other than newline and tab and the Unicode format characters (category Cf: bidi
controls, zero-width characters) except U+200D, the zero-width joiner combined emoji need, trims, and turns three or more consecutive newlines into two; the result must have 2 to 1000 characters. The comment's locale is the player's locale.
5 comments per player per hour.

Credential: a player token (`fp1`), sent as `Authorization: Bearer <token>`. A game token (`fg1`) answers 401 here.

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `gameId` | path | string | yes | Game slug |

### Request body

`application/json`, required.

| Property | Type | Required | Description |
| --- | --- | --- | --- |
| `body` | string | yes | Plain text; 2 to 1000 characters |

### Responses

| Status | Body | Description |
| --- | --- | --- |
| 201 | [Comment](/docs/api/reference/schemas/#schema-comment) | The comment. |
| 400 | [Error](/docs/api/reference/schemas/#schema-error) | Validation failed. |
| 401 | [Error](/docs/api/reference/schemas/#schema-error) | Missing or invalid token. |
| 403 | none | A guest: "Sign in to comment.". |
| 404 | none | Game not in this site's catalog. |
| 429 | [Error](/docs/api/reference/schemas/#schema-error) | Too many requests. Headers: `Retry-After`. |
| 503 | none | Redis is down; the rate limit cannot be checked. |

<RouteHeading id="patch-v1-sdk-comments-commentid" method="PATCH" path="/v1/sdk/comments/{commentId}" />

<CodePanel title="PATCH /v1/sdk/comments/{commentId}">
  <Fragment slot="curl">

    ```sh
    curl -X PATCH 'https://api.filudi.com/v1/sdk/comments/00000000-0000-0000-0000-000000000000' \
      -H "authorization: Bearer $FILUDI_PLAYER_TOKEN" \
      -H 'content-type: application/json' \
      -d '{
      "body": "string"
    }'
    ```

  </Fragment>
  <Fragment slot="javascript">

    ```js
    // playerToken: the fp1 token of POST /v1/sdk/players
    const response = await fetch('https://api.filudi.com/v1/sdk/comments/00000000-0000-0000-0000-000000000000', {
      method: 'PATCH',
      headers: { authorization: `Bearer ${playerToken}`, 'content-type': 'application/json' },
      body: JSON.stringify({
        body: 'string',
      }),
    });
    const data = await response.json();
    ```

  </Fragment>
  <Fragment slot="python">

    ```py
    import requests

    # player_token: the fp1 token of POST /v1/sdk/players
    response = requests.patch(
        'https://api.filudi.com/v1/sdk/comments/00000000-0000-0000-0000-000000000000',
        headers={'authorization': f'Bearer {player_token}'},
        json={
            'body': 'string',
        },
    )
    response.raise_for_status()
    data = response.json()
    ```

  </Fragment>
</CodePanel>

**Change the body of one of the player's comments**

Same body rules as a new comment. Sets `editedAt`. 30 edits per player per hour.

Credential: a player token (`fp1`), sent as `Authorization: Bearer <token>`. A game token (`fg1`) answers 401 here.

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `commentId` | path | string (uuid) | yes |  |

### Request body

`application/json`, required.

| Property | Type | Required | Description |
| --- | --- | --- | --- |
| `body` | string | yes | Plain text; 2 to 1000 characters |

### Responses

| Status | Body | Description |
| --- | --- | --- |
| 200 | [Comment](/docs/api/reference/schemas/#schema-comment) | The comment. |
| 400 | [Error](/docs/api/reference/schemas/#schema-error) | Validation failed. |
| 401 | [Error](/docs/api/reference/schemas/#schema-error) | Missing or invalid token. |
| 404 | none | Unknown, hidden, or written by another player. |
| 429 | [Error](/docs/api/reference/schemas/#schema-error) | Too many requests. Headers: `Retry-After`. |
| 503 | none | Redis is down; the rate limit cannot be checked. |

<RouteHeading id="delete-v1-sdk-comments-commentid" method="DELETE" path="/v1/sdk/comments/{commentId}" />

<CodePanel title="DELETE /v1/sdk/comments/{commentId}">
  <Fragment slot="curl">

    ```sh
    curl -X DELETE 'https://api.filudi.com/v1/sdk/comments/00000000-0000-0000-0000-000000000000' \
      -H "authorization: Bearer $FILUDI_PLAYER_TOKEN"
    ```

  </Fragment>
  <Fragment slot="javascript">

    ```js
    // playerToken: the fp1 token of POST /v1/sdk/players
    const response = await fetch('https://api.filudi.com/v1/sdk/comments/00000000-0000-0000-0000-000000000000', {
      method: 'DELETE',
      headers: { authorization: `Bearer ${playerToken}` },
    });
    // No body: check response.ok.
    ```

  </Fragment>
  <Fragment slot="python">

    ```py
    import requests

    # player_token: the fp1 token of POST /v1/sdk/players
    response = requests.delete(
        'https://api.filudi.com/v1/sdk/comments/00000000-0000-0000-0000-000000000000',
        headers={'authorization': f'Bearer {player_token}'},
    )
    response.raise_for_status()
    # No body: the status says it all.
    ```

  </Fragment>
</CodePanel>

**Delete one of the player's comments**

Works on a hidden comment too.

Credential: a player token (`fp1`), sent as `Authorization: Bearer <token>`. A game token (`fg1`) answers 401 here.

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `commentId` | path | string (uuid) | yes |  |

### Responses

| Status | Body | Description |
| --- | --- | --- |
| 204 | none | Deleted. |
| 401 | [Error](/docs/api/reference/schemas/#schema-error) | Missing or invalid token. |
| 404 | none | Unknown. |

<RouteHeading id="get-v1-catalog-site-games-slug-reviews" method="GET" path="/v1/catalog/{site}/games/{slug}/reviews" />

<CodePanel title="GET /v1/catalog/{site}/games/{slug}/reviews">
  <Fragment slot="curl">

    ```sh
    curl 'https://api.filudi.com/v1/catalog/filudi/games/gloop/reviews'
    ```

  </Fragment>
  <Fragment slot="javascript">

    ```js
    const response = await fetch('https://api.filudi.com/v1/catalog/filudi/games/gloop/reviews');
    const data = await response.json();
    ```

  </Fragment>
  <Fragment slot="python">

    ```py
    import requests

    response = requests.get('https://api.filudi.com/v1/catalog/filudi/games/gloop/reviews')
    response.raise_for_status()
    data = response.json()
    ```

  </Fragment>
</CodePanel>

**Rating and visible comments of a game, newest first**

Public (ADR 0012). Leaves out hidden comments and the comments of players scheduled for deletion.
The nickname is the author's at read time. Every page repeats `rating`.

Credential: none. The route is public.

### Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `site` | path | string | yes | Site slug |
| `slug` | path | string | yes |  |
| `cursor` | query | string | no | Opaque; the `nextCursor` of the previous page, with the same filters and sort; at most 200 characters |
| `limit` | query | integer | no | 1 to 50; default `20` |

### Responses

| Status | Body | Description |
| --- | --- | --- |
| 200 | [GameReviews](/docs/api/reference/schemas/#schema-gamereviews) | Reviews. Headers: `Cache-Control`, `ETag`. |
| 304 | none | Not modified: `If-None-Match` matches the current ETag. |
| 400 | none | Cursor not issued by this route. |
| 404 | none | Unknown site. |
