Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add new API endpoints for forums and topics #11742

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
97 changes: 90 additions & 7 deletions app/Http/Controllers/Forum/ForumsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,43 @@
use App\Transformers\Forum\ForumCoverTransformer;
use Auth;

/**
* @group Forum
*/
class ForumsController extends Controller
{
public function __construct()
{
parent::__construct();

$this->middleware('require-scopes:public', ['only' => ['index', 'show']]);
}

/**
* Get Forum Listing
*
* Get top-level forums, their subforums (max 2 deep), and their last topics.
*
* ---
*
* ### Response Format
*
* Field | Type |
* ----------- | ---------------------------- |
* forums | [Forum](#forum)[] |
* last_topics | [ForumTopic](#forum-topic)[] |
*
* @response {
* "forums": [
* { "forum_id": 1, "...": "..." },
* { "forum_id": 2, "...": "..." }
* ],
* "last_topics": [
* { "id": 1, "...": "..." },
* { "id": 2, "...": "..." }
* ]
* }
*/
public function index()
{
$forums = Forum
Expand All @@ -33,6 +63,13 @@ public function index()
return priv_check('ForumView', $forum)->can();
});

if (is_api_request()) {
return [
'forums' => json_collection($forums, 'Forum/Forum', ['subforums.subforums']),
'last_topics' => json_collection($lastTopics, 'Forum/Topic'),
];
}

return ext_view('forum.forums.index', compact('forums', 'lastTopics'));
}

Expand All @@ -53,6 +90,40 @@ public function markAsRead()
return ext_view('layout.ujs-reload', [], 'js');
}

/**
* Get Forum and Topics
*
* Get a forum by id, its pinned topics, recent topics, its subforums, and the subforums' last topics.
*
* ---
*
* ### Response Format
*
* Field | Type |
* ------------- | ---------------------------- |
* forum | [Forum](#forum) |
* topics | [ForumTopic](#forum-topic)[] |
* last_topics | [ForumTopic](#forum-topic)[] |
* pinned_topics | [ForumTopic](#forum-topic)[] |
*
* @urlParam forum integer required Id of the forum. Example: 1
*
* @response {
* "forum": { "id": 1, "...": "..." },
* "topics": [
* { "id": 1, "...": "..." },
* { "id": 2, "...": "..." },
* ],
* "last_topics": [
* { "id": 1, "...": "..." },
* { "id": 2, "...": "..." },
* ],
* "pinned_topics": [
* { "id": 1, "...": "..." },
* { "id": 2, "...": "..." },
* ]
* }
*/
public function show($id)
{
$params = get_params(request()->all(), null, [
Expand All @@ -70,11 +141,6 @@ public function show($id)

priv_check('ForumView', $forum)->ensureCan();

$cover = json_item(
$forum->cover()->firstOrNew([]),
new ForumCoverTransformer()
);

$showDeleted = priv_check('ForumModerate', $forum)->can();

$pinnedTopics = $forum->topics()
Expand All @@ -88,8 +154,20 @@ public function show($id)
->with('forum')
->normal()
->showDeleted($showDeleted)
->recent(compact('sort', 'withReplies'))
->paginate();
->recent(compact('sort', 'withReplies'));

if (is_api_request()) {
$topics->limit(Topic::PER_PAGE);

return [
'forum' => json_item($forum, 'Forum/Forum', ['subforums.subforums']),
'topics' => json_collection($topics, 'Forum/Topic'),
'last_topics' => json_collection($lastTopics, 'Forum/Topic'),
'pinned_topics' => json_collection($pinnedTopics, 'Forum/Topic'),
];
}

$topics = $topics->paginate();

$allTopics = array_merge($pinnedTopics->all(), $topics->all());
$topicReadStatus = TopicTrack::readStatus($user, $allTopics);
Expand All @@ -103,6 +181,11 @@ public function show($id)
->get()
->keyBy('topic_id');

$cover = json_item(
$forum->cover()->firstOrNew([]),
new ForumCoverTransformer()
);

$noindex = !$forum->enable_indexing;

set_opengraph($forum);
Expand Down
51 changes: 50 additions & 1 deletion app/Http/Controllers/Forum/TopicsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ public function __construct()
'store',
]]);

$this->middleware('require-scopes:public', ['only' => ['show']]);
$this->middleware('require-scopes:public', ['only' => ['index', 'show']]);
$this->middleware('require-scopes:forum.write', ['only' => ['reply', 'store', 'update']]);
}

Expand Down Expand Up @@ -279,6 +279,55 @@ public function reply($id)
}
}

/**
* Get Topic Listing
*
* Get a sorted list of topics, optionally from a specific forum
*
* ---
*
* ### Response Format
*
* Field | Type | Notes
* ------------- | ----------------------------- | -----
* topics | [ForumTopic](#forum-topic)[] | |
* cursor_string | [CursorString](#cursorstring) | |
*
* @usesCursor
* @queryParam forum_id Id of a specific forum to get topics from. No-example
* @queryParam sort Topic sorting option. Valid values are `new` (default) and `old`. Both sort by the topic's last post time. No-example
* @queryParam limit Maximum number of topics to be returned (50 at most and by default). No-example
*
* @response {
* "topics": [
* { "id": 1, "...": "..." },
* { "id": 2, "...": "..." }
* ],
* "cursor_string": "eyJoZWxsbyI6IndvcmxkIn0"
* }
*/
public function index()
{
$params = request()->all();
$limit = \Number::clamp(get_int($params['limit'] ?? null) ?? Topic::PER_PAGE, 1, Topic::PER_PAGE);
$cursorHelper = Topic::makeDbCursorHelper($params['sort'] ?? null);

$topics = Topic::cursorSort($cursorHelper, cursor_from_params($params))
->limit($limit);

$forum_id = get_int($params['forum_id'] ?? null) ?? null;
if ($forum_id !== null) {
$topics->where('forum_id', $forum_id);
}

[$topics, $hasMore] = $topics->getWithHasMore();

return [
'topics' => json_collection($topics, 'Forum/Topic'),
...cursor_for_response($cursorHelper->next($topics, $hasMore)),
];
}

/**
* Get Topic and Posts
*
Expand Down
13 changes: 13 additions & 0 deletions app/Models/Forum/Topic.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
use App\Models\Beatmapset;
use App\Models\Log;
use App\Models\Notification;
use App\Models\Traits\WithDbCursorHelper;
use App\Models\User;
use App\Traits\Memoizes;
use App\Traits\Validatable;
Expand Down Expand Up @@ -78,8 +79,20 @@
use SoftDeletes {
restore as private origRestore;
}
use WithDbCursorHelper;

const DEFAULT_SORT = 'new';
const SORTS = [
'new' => [
// type 'timestamp' because the values are stored as integer in the database
['column' => 'topic_last_post_time', 'order' => 'DESC', 'type' => 'timestamp'],
['column' => 'topic_last_post_id', 'order' => 'DESC']

Check failure on line 89 in app/Models/Forum/Topic.php

View workflow job for this annotation

GitHub Actions / Lint all

Multi-line arrays must have a trailing comma after the last element.
],
'old' => [
['column' => 'topic_last_post_time', 'order' => 'ASC', 'type' => 'timestamp'],
['column' => 'topic_last_post_id', 'order' => 'ASC']

Check failure on line 93 in app/Models/Forum/Topic.php

View workflow job for this annotation

GitHub Actions / Lint all

Multi-line arrays must have a trailing comma after the last element.
]

Check failure on line 94 in app/Models/Forum/Topic.php

View workflow job for this annotation

GitHub Actions / Lint all

Multi-line arrays must have a trailing comma after the last element.
];

const STATUS_LOCKED = 1;
const STATUS_UNLOCKED = 0;
Expand Down
34 changes: 34 additions & 0 deletions app/Transformers/Forum/ForumTransformer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php

// Copyright (c) ppy Pty Ltd <[email protected]>. Licensed under the GNU Affero General Public License v3.0.
// See the LICENCE file in the repository root for full licence text.

namespace App\Transformers\Forum;

use App\Models\Forum\Forum;
use App\Transformers\TransformerAbstract;
use League\Fractal\Resource\ResourceInterface;

class ForumTransformer extends TransformerAbstract
{
protected array $availableIncludes = [
'subforums',
];

public function transform(Forum $forum): array
{
return [
'id' => $forum->forum_id,
'name' => $forum->forum_name,
'description' => $forum->forum_desc,
];
}

public function includeSubforums(Forum $forum): ResourceInterface
{
return $this->collection(
$forum->subforums,
new ForumTransformer()
);
}
}
11 changes: 11 additions & 0 deletions app/helpers.php
Original file line number Diff line number Diff line change
Expand Up @@ -1608,6 +1608,8 @@ function get_param_value($input, $type)
return get_arr($input, 'get_int');
case 'time':
return parse_time_to_carbon($input);
case 'timestamp':
return parse_time_to_timestamp($input);
default:
return presence(get_string($input));
}
Expand Down Expand Up @@ -1746,6 +1748,15 @@ function parse_time_to_carbon($value)
}
}

function parse_time_to_timestamp($value)
{
$time = parse_time_to_carbon($value);

if ($time instanceof Carbon\Carbon) {
return $time->timestamp;
}
}

function format_duration_for_display(int $seconds)
{
return floor($seconds / 60).':'.str_pad((string) ($seconds % 60), 2, '0', STR_PAD_LEFT);
Expand Down
10 changes: 10 additions & 0 deletions resources/views/docs/_structures/forum.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<div id="forum-object" data-unique="forum-object"></div>

## Forum

Field | Type | Notes
------------|---------------------------|-------
id | integer |
name | string |
description | string |
subforums | [Forum](#forum-object)[]? | Maximum 2 layers of subforums from the top-level Forum
3 changes: 2 additions & 1 deletion routes/web.php
Original file line number Diff line number Diff line change
Expand Up @@ -478,9 +478,10 @@
Route::group(['as' => 'forum.', 'namespace' => 'Forum'], function () {
Route::group(['prefix' => 'forums'], function () {
Route::post('topics/{topic}/reply', 'TopicsController@reply')->name('topics.reply');
Route::resource('topics', 'TopicsController', ['only' => ['show', 'store', 'update']]);
Route::resource('topics', 'TopicsController', ['only' => ['index', 'show', 'store', 'update']]);
Route::resource('posts', 'PostsController', ['only' => ['update']]);
});
Route::resource('forums', 'ForumsController', ['only' => ['index', 'show']]);
});
Route::resource('matches', 'MatchesController', ['only' => ['index', 'show']]);

Expand Down
48 changes: 48 additions & 0 deletions tests/api_routes.json
Original file line number Diff line number Diff line change
Expand Up @@ -751,6 +751,22 @@
"forum.write"
]
},
{
"uri": "api/v2/forums/topics",
"methods": [
"GET",
"HEAD"
],
"controller": "App\\Http\\Controllers\\Forum\\TopicsController@index",
"middlewares": [
"App\\Http\\Middleware\\ThrottleRequests:1200,1,api:",
"App\\Http\\Middleware\\RequireScopes",
"App\\Http\\Middleware\\RequireScopes:public"
],
"scopes": [
"public"
]
},
{
"uri": "api/v2/forums/topics/{topic}",
"methods": [
Expand Down Expand Up @@ -799,6 +815,38 @@
"forum.write"
]
},
{
"uri": "api/v2/forums",
"methods": [
"GET",
"HEAD"
],
"controller": "App\\Http\\Controllers\\Forum\\ForumsController@index",
"middlewares": [
"App\\Http\\Middleware\\ThrottleRequests:1200,1,api:",
"App\\Http\\Middleware\\RequireScopes",
"App\\Http\\Middleware\\RequireScopes:public"
],
"scopes": [
"public"
]
},
{
"uri": "api/v2/forums/{forum}",
"methods": [
"GET",
"HEAD"
],
"controller": "App\\Http\\Controllers\\Forum\\ForumsController@show",
"middlewares": [
"App\\Http\\Middleware\\ThrottleRequests:1200,1,api:",
"App\\Http\\Middleware\\RequireScopes",
"App\\Http\\Middleware\\RequireScopes:public"
],
"scopes": [
"public"
]
},
{
"uri": "api/v2/matches",
"methods": [
Expand Down
Loading